Cách chặn luồng mã cho đến khi một sự kiện được kích hoạt trong C #


9

Ở đây chúng tôi có một Gridvới a Button. Khi người dùng nhấp vào nút, một phương thức trong lớp Utility được thực thi để buộc ứng dụng nhận được nhấp chuột vào Grid. Luồng mã phải dừng ở đây và không tiếp tục cho đến khi người dùng đã nhấp vào Grid.

Tôi đã có một câu hỏi tương tự trước đây:

Đợi đến khi người dùng nhấp vào C # WPF

Trong câu hỏi đó, tôi đã nhận được câu trả lời bằng cách sử dụng async / await, nó hoạt động, nhưng vì tôi sẽ sử dụng nó như một phần của API, tôi không muốn sử dụng async / await, vì người tiêu dùng sau đó sẽ đánh dấu các phương thức của họ bằng async mà tôi không muốn.

Làm thế nào để tôi viết Utility.PickPoint(Grid grid)phương pháp để đạt được mục tiêu này?

Tôi thấy điều này có thể giúp nhưng không hoàn toàn hiểu nó để áp dụng ở đây để trung thực:

Chặn cho đến khi một sự kiện hoàn thành

Hãy xem nó như một cái gì đó giống như phương thức Console.ReadKey () trong ứng dụng Console. Khi chúng ta gọi phương thức này, luồng mã dừng lại cho đến khi chúng ta nhập một số giá trị. Trình gỡ lỗi không tiếp tục cho đến khi chúng tôi nhập một cái gì đó. Tôi muốn hành vi chính xác cho phương thức PickPoint (). Luồng mã sẽ dừng cho đến khi người dùng nhấp vào Lưới.

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="3*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid x:Name="View" Background="Green"/>
        <Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
    </Grid>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        // do not continue the code flow until the user has clicked on the grid. 
        // so when we debug, the code flow will literally stop here.
        var point = Utility.PickPoint(View);


        MessageBox.Show(point.ToString());
    }
}

public static class Utility
{
    public static Point PickPoint(Grid grid)
    {

    }
}

Rõ ràng là Aync/Awaitcách thực hiện Thao tác A và lưu hoạt động đó STATE ngay bây giờ bạn muốn người dùng đó nên nhấp vào Lưới .. vì vậy nếu người dùng Nhấp vào Lưới bạn kiểm tra trạng thái nếu đúng thì thao tác của bạn có làm gì khác không?
Rao Hammas Hussain

@Rao HammasHussain Tôi đã cập nhật câu hỏi của mình với một liên kết có thể giúp ích. Phương thức tiện ích sẽ là một phần của API mà người dùng API sẽ gọi bất cứ khi nào anh ta muốn yêu cầu người dùng cuối nhấp vào màn hình. Hãy xem nó như một cửa sổ nhắc cho văn bản trong các ứng dụng windows bình thường hoặc phương thức Console.Readline (). Trong những trường hợp này, dòng mã dừng lại cho đến khi người dùng nhập một cái gì đó. Bây giờ tôi muốn điều chính xác nhưng lần này người dùng nhấp vào màn hình.
Vahid

AutoResetEventkhông phải là những gì bạn muốn?
Rao Hammas Hussain

@Rao HammasHussain Tôi nghĩ vậy nhưng thực sự không biết sử dụng nó ở đây.
Vahid

Giống như bạn đang cố tình thực hiện WAIT STATE. có thực sự cần thiết không? Vì bạn không thể đặt cái này var point = Utility.PickPoint(Grid grid);trong phương thức Grid Click? làm một số hoạt động và trả lại phản ứng?
Rao Hammas Hussain

Câu trả lời:


7

"Làm thế nào để chặn dòng mã cho đến khi một sự kiện được kích hoạt?"

Bạn tiếp cận là sai. Hướng sự kiện không có nghĩa là chặn và chờ đợi một sự kiện. Bạn không bao giờ chờ đợi, ít nhất bạn luôn cố gắng hết sức để tránh nó. Chờ đợi là lãng phí tài nguyên, chặn các luồng và có thể gây ra nguy cơ bế tắc hoặc luồng zombie (trong trường hợp tín hiệu không bao giờ được đưa ra).
Cần phải rõ ràng rằng việc chặn một chuỗi để chờ đợi một sự kiện là một mô hình chống vì nó mâu thuẫn với ý tưởng của một sự kiện.

Bạn thường có hai tùy chọn (hiện đại): triển khai API không đồng bộ hoặc API hướng sự kiện. Vì bạn không muốn triển khai API không đồng bộ, nên bạn chỉ còn lại API theo hướng sự kiện.

Chìa khóa của API hướng sự kiện là, thay vì buộc người gọi phải chờ đồng bộ kết quả hoặc thăm dò kết quả, bạn hãy để người gọi tiếp tục và gửi thông báo cho anh ta khi kết quả đã sẵn sàng hoặc hoạt động đã hoàn tất. Trong khi đó, người gọi có thể tiếp tục thực hiện các hoạt động khác.

Khi xem xét vấn đề từ góc độ luồng, thì API hướng sự kiện cho phép luồng gọi, ví dụ, luồng UI, thực thi trình xử lý sự kiện của nút, được tự do tiếp tục xử lý các hoạt động liên quan đến giao diện người dùng, như kết xuất các thành phần UI hoặc xử lý đầu vào của người dùng như di chuyển chuột và nhấn phím. Hiệu ứng tương tự như API không đồng bộ, mặc dù ít thuận tiện hơn.

Vì bạn không cung cấp đủ chi tiết về những gì bạn đang thực sự cố gắng, những gì Utility.PickPoint()thực sự đang làm và kết quả của nhiệm vụ là gì hoặc tại sao người dùng phải nhấp vào `Grid, tôi không thể cung cấp cho bạn một giải pháp tốt hơn . Tôi chỉ có thể cung cấp một mô hình chung về cách thực hiện yêu cầu của bạn.

Luồng hoặc mục tiêu của bạn rõ ràng được chia thành ít nhất hai bước để biến nó thành một chuỗi các hoạt động:

  1. Thực hiện thao tác 1, khi người dùng nhấp vào nút
  2. Thực hiện thao tác 2 (tiếp tục / hoàn thành thao tác 1), khi người dùng nhấp vào Grid

với ít nhất hai ràng buộc:

  1. Tùy chọn: chuỗi phải được hoàn thành trước khi ứng dụng khách API được phép lặp lại. Một chuỗi được hoàn thành sau khi hoạt động 2 đã chạy đến hoàn thành.
  2. Hoạt động 1 luôn được thực hiện trước khi hoạt động 2. Hoạt động 1 bắt đầu chuỗi.
  3. Hoạt động 1 phải hoàn thành trước khi máy khách API được phép thực hiện thao tác 2

Điều này yêu cầu hai thông báo cho ứng dụng khách API để cho phép tương tác không chặn:

  1. Thao tác 1 đã hoàn thành (hoặc yêu cầu tương tác)
  2. Hoạt động 2 (hoặc mục tiêu) đã hoàn thành

Bạn nên để API của mình thực hiện hành vi và các ràng buộc này bằng cách hiển thị hai phương thức chung và hai sự kiện chung.

Triển khai / tái cấu trúc API tiện ích

Tiện ích.cs

class Utility
{
  public event EventHandler InitializePickPointCompleted;
  public event EventHandler<PickPointCompletedEventArgs> PickPointCompleted;
  private bool IsPickPointInitialized { get; set; }
  private bool IsExecutingSequence { get; set; }

  // The prefix 'Begin' signals the caller or client of the API, 
  // that he also has to end the sequence explicitly
  public void BeginPickPoint(param)
  {
    // Implement constraint 1
    if (this.IsExecutingSequence)
    {
      // Alternatively just return or use Try-do pattern
      throw new InvalidOperationException("BeginPickPoint is already executing. Call EndPickPoint before starting another sequence.");
    }

    // Set the flag that a current sequence is in progress
    this.IsExecutingSequence = true;

    // Execute operation until caller interaction is required.
    // Execute in background thread to allow API caller to proceed with execution.
    Task.Run(() => StartOperationNonBlocking(param));
  }

  public void EndPickPoint(param)
  {
    // Implement constraint 2 and 3
    if (!this.IsPickPointInitialized)
    {
      // Alternatively just return or use Try-do pattern
      throw new InvalidOperationException("BeginPickPoint must have completed execution before calling EndPickPoint.");
    }

    // Execute operation until caller interaction is required.
    // Execute in background thread to allow API caller to proceed with execution.
    Task.Run(() => CompleteOperationNonBlocking(param));
  }

  private void StartOperationNonBlocking(param)
  {
    ... // Do something

    // Flag the completion of the first step of the sequence (to guarantee constraint 2)
    this.IsPickPointInitialized = true;

    // Request caller interaction to kick off EndPickPoint() execution
    OnInitializePickPointCompleted();
  }

  private void CompleteOperationNonBlocking(param)
  {
    // Execute goal and get the result of the completed task
    Point result = ExecuteGoal();

    // Reset API sequence
    this.IsExecutingSequence = false;
    this.IsPickPointInitialized = false;

    // Notify caller that execution has completed and the result is available
    OnPickPointCompleted(result);
  }

  private void OnInitializePickPointCompleted()
  {
    // Set the result of the task
    this.InitializePickPointCompleted?.Invoke(this, EventArgs.Empty);
  }

  private void OnPickPointCompleted(Point result)
  {
    // Set the result of the task
    this.PickPointCompleted?.Invoke(this, new PickPointCompletedEventArgs(result));
  }
}

PickPointCompletedEventArss.cs

class PickPointCompletedEventArgs : EventArgs
{
  public Point Result { get; }

  public PickPointCompletedEventArgs(Point result)
  {
    this.Result = result;
  }
}

Sử dụng API

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private Utility Api { get; set; }

  public MainWindow()
  {
    InitializeComponent();

    this.Api = new Utility();
  }

  private void StartPickPoint_OnButtonClick(object sender, RoutedEventArgs e)
  {
    this.Api.InitializePickPointCompleted += RequestUserInput_OnInitializePickPointCompleted;

    // Invoke API and continue to do something until the first step has completed.
    // This is possible because the API will execute the operation on a background thread.
    this.Api.BeginPickPoint();
  }

  private void RequestUserInput_OnInitializePickPointCompleted(object sender, EventArgs e)
  {
    // Cleanup
    this.Api.InitializePickPointCompleted -= RequestUserInput_OnInitializePickPointCompleted;

    // Communicate to the UI user that you are waiting for him to click on the screen
    // e.g. by showing a Popup, dimming the screen or showing a dialog.
    // Once the input is received the input event handler will invoke the API to complete the goal   
    MessageBox.Show("Please click the screen");  
  }

  private void FinishPickPoint_OnGridMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  {
    this.Api.PickPointCompleted += ShowPoint_OnPickPointCompleted;

    // Invoke API to complete the goal
    // and continue to do something until the last step has completed
    this.Api.EndPickPoint();
  }

  private void ShowPoint_OnPickPointCompleted(object sender, PickPointCompletedEventArgs e)
  {
    // Cleanup
    this.Api.PickPointCompleted -= ShowPoint_OnPickPointCompleted;

    // Get the result from the PickPointCompletedEventArgs instance
    Point point = e.Result;

    // Handle the result
    MessageBox.Show(point.ToString());
  }
}

MainWindow.xaml

<Window>
  <Grid MouseLeftButtonUp="FinishPickPoint_OnGridMouseLeftButtonUp">
    <Button Click="StartPickPoint_OnButtonClick" />
  </Grid>
</Window>

Nhận xét

Các sự kiện được nêu trên một luồng nền sẽ thực thi các trình xử lý của chúng trên cùng một luồng. Truy cập một DispatcherObjectphần tử UI giống như một trình xử lý, được thực thi trên luồng nền, đòi hỏi thao tác quan trọng phải được Dispatchersử dụng để sử dụng Dispatcher.Invokehoặc Dispatcher.InvokeAsyncđể tránh các ngoại lệ xuyên luồng.


Một số suy nghĩ - trả lời ý kiến ​​của bạn

Bởi vì bạn đang tiếp cận tôi để tìm giải pháp chặn "tốt hơn", cho tôi ví dụ về các ứng dụng bảng điều khiển, tôi cảm thấy thuyết phục bạn rằng nhận thức hoặc quan điểm của bạn là hoàn toàn sai.

"Hãy xem xét một ứng dụng Console có hai dòng mã này.

var str = Console.ReadLine(); 
Console.WriteLine(str);

Điều gì xảy ra khi bạn thực thi ứng dụng trong chế độ gỡ lỗi. Nó sẽ dừng ở dòng mã đầu tiên và buộc bạn nhập một giá trị trong Bảng điều khiển UI và sau đó sau khi bạn nhập một cái gì đó và nhấn Enter, nó sẽ thực thi dòng tiếp theo và thực sự in những gì bạn đã nhập. Tôi đã suy nghĩ về chính xác hành vi tương tự nhưng trong ứng dụng WPF. "

Một ứng dụng giao diện điều khiển là một cái gì đó hoàn toàn khác nhau. Khái niệm luồng là khác nhau. Các ứng dụng bảng điều khiển không có GUI. Chỉ cần luồng đầu vào / đầu ra / lỗi. Bạn không thể so sánh kiến ​​trúc của ứng dụng bảng điều khiển với ứng dụng GUI phong phú. Điều này sẽ không làm việc. Bạn thực sự phải hiểu và chấp nhận điều này.

WPF được xây dựng xung quanh một luồng kết xuất và một luồng UI. Các luồng đó luôn luôn quay để giao tiếp với HĐH như xử lý đầu vào của người dùng - giữ cho ứng dụng phản hồi nhanh . Bạn không bao giờ muốn tạm dừng / chặn luồng này vì nó sẽ ngăn khung làm việc nền thiết yếu (như phản hồi các sự kiện chuột - bạn không muốn chuột đóng băng):

chờ đợi = chặn chủ đề = không phản hồi = xấu UX = người dùng / khách hàng khó chịu = rắc rối trong văn phòng.

Đôi khi, luồng ứng dụng yêu cầu đợi đầu vào hoặc một thói quen hoàn thành. Nhưng chúng tôi không muốn chặn luồng chính.
Đó là lý do tại sao mọi người phát minh ra các mô hình lập trình không đồng bộ phức tạp, để cho phép chờ mà không chặn luồng chính và không buộc nhà phát triển phải viết mã đa luồng phức tạp và sai lầm.

Mỗi khung ứng dụng hiện đại cung cấp các hoạt động không đồng bộ hoặc mô hình lập trình không đồng bộ, để cho phép phát triển mã đơn giản và hiệu quả.

Thực tế là bạn đang cố gắng hết sức để chống lại mô hình lập trình không đồng bộ, cho thấy sự thiếu hiểu biết đối với tôi. Mọi nhà phát triển hiện đại đều thích API không đồng bộ hơn API đồng bộ. Không có nhà phát triển nghiêm túc quan tâm đến việc sử dụng awaittừ khóa hoặc để tuyên bố phương pháp của mình async. Không ai. Bạn là người đầu tiên tôi gặp phải phàn nàn về API không đồng bộ và là người thấy chúng bất tiện khi sử dụng.

Nếu tôi sẽ kiểm tra khuôn khổ của bạn, mục tiêu để giải quyết các vấn đề liên quan đến UI hoặc làm cho các tác vụ liên quan đến UI dễ dàng hơn, tôi sẽ mong đợi nó không đồng bộ - tất cả các cách.
API liên quan đến giao diện người dùng không đồng bộ là lãng phí, vì nó sẽ làm phức tạp phong cách lập trình của tôi, do đó mã của tôi do đó dễ bị lỗi hơn và khó bảo trì.

Một viễn cảnh khác: khi bạn thừa nhận rằng việc chờ đợi sẽ chặn luồng UI, tạo ra trải nghiệm người dùng rất tệ và không mong muốn vì UI sẽ đóng băng cho đến khi chờ đợi kết thúc, bây giờ bạn nhận ra điều này, tại sao bạn lại cung cấp mô hình API hoặc plugin khuyến khích một nhà phát triển để làm chính xác điều này - thực hiện chờ đợi?
Bạn không biết plugin của bên thứ 3 sẽ làm gì và một thói quen sẽ mất bao lâu cho đến khi hoàn thành. Đây đơn giản là một thiết kế API tồi. Khi API của bạn hoạt động trên luồng UI thì người gọi API của bạn phải có thể thực hiện các cuộc gọi không chặn đến nó.

Nếu bạn từ chối giải pháp rẻ tiền hoặc duyên dáng hơn là sử dụng cách tiếp cận theo hướng sự kiện như trong ví dụ của tôi.
Nó thực hiện những gì bạn muốn: bắt đầu một thói quen - chờ người dùng nhập vào - tiếp tục thực hiện - hoàn thành mục tiêu.

Tôi thực sự đã cố gắng nhiều lần để giải thích tại sao chờ đợi / chặn là một thiết kế ứng dụng tồi. Một lần nữa, bạn không thể so sánh giao diện người dùng bảng điều khiển với giao diện người dùng đồ họa phong phú, trong đó, ví dụ xử lý đầu vào đơn giản là vô cùng phức tạp hơn là chỉ nghe luồng đầu vào. Tôi thực sự không biết mức độ kinh nghiệm của bạn và nơi bạn bắt đầu, nhưng bạn nên bắt đầu chấp nhận mô hình lập trình không đồng bộ. Tôi không biết lý do tại sao bạn cố gắng tránh nó. Nhưng nó không khôn ngoan chút nào.

Ngày nay các mô hình lập trình không đồng bộ được triển khai ở mọi nơi, trên mọi nền tảng, trình biên dịch, mọi môi trường, trình duyệt, máy chủ, máy tính để bàn, cơ sở dữ liệu - ở mọi nơi. Mô hình hướng sự kiện cho phép đạt được cùng một mục tiêu, nhưng nó ít thuận tiện hơn để sử dụng (đăng ký / hủy đăng ký / từ các sự kiện), dựa trên các chủ đề nền. Hướng sự kiện là kiểu cũ và chỉ nên được sử dụng khi thư viện không đồng bộ không có sẵn hoặc không áp dụng.

"Tôi đã thấy hành vi chính xác trong Autodesk Revit."

Hành vi (những gì bạn trải nghiệm hoặc quan sát) khác nhiều so với cách thực hiện trải nghiệm này. Hai điều khác nhau. Autodesk của bạn rất có thể sử dụng các thư viện hoặc tính năng ngôn ngữ không đồng bộ hoặc một số cơ chế phân luồng khác. Và nó cũng là bối cảnh liên quan. Khi phương thức trong tâm trí bạn đang thực thi trên một luồng nền thì nhà phát triển có thể chọn chặn luồng này. Anh ta có một lý do rất tốt để làm điều này hoặc chỉ đưa ra một lựa chọn thiết kế tồi. Bạn đang hoàn toàn đi sai đường;) Chặn là không tốt.
(Mã nguồn Autodesk có phải là nguồn mở không? Hoặc làm thế nào để bạn biết cách triển khai?)

Tôi không muốn xúc phạm bạn, xin hãy tin tôi. Nhưng vui lòng xem xét lại để triển khai API không đồng bộ. Chỉ trong đầu bạn, các nhà phát triển không muốn sử dụng async / await. Bạn rõ ràng đã có suy nghĩ sai lầm. Và quên đi đối số ứng dụng bảng điều khiển đó - nó vô nghĩa;)

API liên quan đến giao diện người dùng PHẢI sử dụng async / chờ đợi bất cứ khi nào có thể. Mặt khác, bạn để lại tất cả công việc để viết mã không chặn cho máy khách API của bạn. Bạn sẽ buộc tôi bọc mọi cuộc gọi đến API của bạn thành một chuỗi nền. Hoặc để sử dụng xử lý sự kiện ít thoải mái. Hãy tin tôi - mọi nhà phát triển thay vì trang trí cho các thành viên của mình async, hơn là xử lý sự kiện. Mỗi khi bạn sử dụng các sự kiện bạn có thể gặp rủi ro rò rỉ bộ nhớ - tùy thuộc vào một số trường hợp, nhưng rủi ro là có thật và không hiếm khi lập trình bất cẩn.

Tôi thực sự hy vọng bạn hiểu tại sao chặn là xấu. Tôi thực sự hy vọng bạn quyết định sử dụng async / await để viết API không đồng bộ hiện đại. Tuy nhiên, tôi đã chỉ cho bạn một cách rất phổ biến để chờ đợi không chặn, sử dụng các sự kiện, mặc dù tôi khuyên bạn nên sử dụng async / await.

"API sẽ cho phép lập trình viên có quyền truy cập vào UI và v.v. Bây giờ giả sử lập trình viên muốn phát triển một bổ trợ mà khi nhấn nút, người dùng cuối được yêu cầu chọn một điểm trong UI"

Nếu bạn không muốn cho phép plugin có quyền truy cập trực tiếp vào các thành phần UI, bạn nên cung cấp giao diện để ủy quyền các sự kiện hoặc hiển thị các thành phần bên trong thông qua các đối tượng được trừu tượng hóa.
API bên trong sẽ đăng ký các sự kiện UI thay mặt cho Bổ trợ và sau đó ủy thác sự kiện bằng cách hiển thị một sự kiện "trình bao bọc" tương ứng cho máy khách API. API của bạn phải cung cấp một số móc nối trong đó Bổ trợ có thể kết nối để truy cập các thành phần ứng dụng cụ thể. API plugin hoạt động như một bộ chuyển đổi hoặc mặt tiền để cung cấp cho bên ngoài quyền truy cập vào bên trong.
Để cho phép một mức độ cô lập.

Hãy xem cách Visual Studio quản lý các plugin hoặc cho phép chúng tôi triển khai chúng. Giả sử bạn muốn viết một plugin cho Visual Studio và thực hiện một số nghiên cứu về cách thực hiện việc này. Bạn sẽ nhận ra rằng Visual Studio trưng bày các phần bên trong của nó thông qua một giao diện hoặc API. Vì bạn có thể thao tác với trình soạn thảo mã hoặc lấy thông tin về nội dung của trình soạn thảo mà không cần truy cập thực sự vào nó.


Xin chào, cảm ơn bạn đã tiếp cận câu hỏi từ một góc độ khác. Xin lỗi nếu câu hỏi hơi mơ hồ về chi tiết. Hãy xem xét một ứng dụng Console có hai dòng mã này. var str = Console.ReadLine(); Console.WriteLine(str);Điều gì xảy ra khi bạn thực thi ứng dụng trong chế độ gỡ lỗi. Nó sẽ dừng ở dòng mã đầu tiên và buộc bạn nhập một giá trị trong Bảng điều khiển UI và sau đó sau khi bạn nhập một cái gì đó và nhấn Enter, nó sẽ thực thi dòng tiếp theo và thực sự in những gì bạn đã nhập. Tôi đã suy nghĩ về chính xác hành vi tương tự nhưng trong ứng dụng WPF.
Vahid

Trong ứng dụng CAD mà tôi đang phát triển, người dùng được cho là có thể mở rộng nó bằng addins / plugin. API sẽ cho phép lập trình viên có quyền truy cập vào UI và v.v. Bây giờ, giả sử lập trình viên muốn phát triển một addin mà khi nhấn nút, người dùng cuối được yêu cầu chọn một điểm trong UI và sau đó mã sẽ làm khác công cụ mát mẻ với các điểm nhất định. Có thể họ sẽ yêu cầu một điểm khác được chọn và vẽ một đường, v.v.
Vahid

Tôi đã thấy hành vi chính xác trong Autodesk Revit.
Vahid

1
Tôi có một cái gì đó để nói về yêu cầu của bạn. Xin vui lòng đọc câu trả lời cập nhật của tôi. Tôi đã đăng bài trả lời ở đó vì nó lâu hơn. Tôi thừa nhận rằng bạn thực sự đã kích hoạt tôi. Xin vui lòng, trong khi đọc hãy nhớ rằng tôi không muốn xúc phạm bạn.
BionicCode

Cảm ơn bạn đã trả lời cập nhật của bạn. Tất nhiên, tôi không bị xúc phạm. Trái lại, tôi thực sự biết ơn vì thời gian và công sức bạn đã dành cho việc này.
Vahid

5

Cá nhân tôi nghĩ rằng điều này quá phức tạp với mọi người, nhưng có lẽ tôi không hoàn toàn hiểu lý do tại sao điều này cần phải được thực hiện theo một cách nhất định, nhưng có vẻ như một kiểm tra bool đơn giản có thể được sử dụng ở đây.

Trước hết, hãy lưới của bạn lần truy cập kiểm chứng bằng cách thiết lập BackgroundIsHitTestVisiblethuộc tính, nếu không nó thậm chí sẽ không cú click chuột chụp.

<grid MouseLeftButtonUp="Grid_MouseLeftButtonUp" IsHitTestVisible="True" Background="Transparent">

Tiếp theo, tạo một giá trị bool có thể lưu trữ xem sự kiện "GridClick" có xảy ra hay không. Khi lưới được nhấp, kiểm tra giá trị đó và thực hiện thực hiện từ sự kiện nhấp vào lưới nếu nó đang chờ nhấp.

Thí dụ:

bool awaitingClick = false;


private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
   awaitingClick=true;
}

private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{     
     //Stop here if the program shouldn't do anything when grid is clicked
     if (!awaitingClick) { return; } 

     //Run event
     var point = Utility.PickPoint(View);
     MessageBox.Show(point.ToString());

     awaitingClick=false;//Reset
}

Xin chào Tronald, tôi nghĩ bạn đã hiểu nhầm câu hỏi. Những gì tôi cần là mã dừng lại ở Utility.PickPoint (Chế độ xem) và chỉ tiếp tục sau khi người dùng đã nhấp vào Lưới.
Vahid

Oh yep, tôi hoàn toàn hiểu lầm. Tôi xin lỗi, tôi không nhận ra bạn cần mọi thứ để thực sự dừng lại. Tôi không nghĩ điều đó là có thể nếu không có đa luồng vì toàn bộ UI sẽ bị chặn.
Tronald

Tôi vẫn không chắc chắn nếu nó không thể. Hoàn toàn có thể với async / await không phải là một giải pháp đa luồng. Nhưng những gì tôi cần là một giải pháp thay thế cho giải pháp async / await.
Vahid

1
Chắc chắn, nhưng bạn đã đề cập rằng bạn không thể sử dụng async / await. Có vẻ như bạn sẽ cần phải sử dụng một bộ điều phối và một luồng được tách ra khỏi luồng chính (thực thi trên UI). Tôi hy vọng bạn tìm thấy một cách khác mặc dù tôi quan tâm đến bản thân mình
Tronald

2

Tôi đã thử một vài thứ nhưng tôi không thể làm được mà không có async/await. Bởi vì nếu chúng tôi không sử dụng nó, nó sẽ gây ra DeadLockhoặc UI bị chặn và sau đó chúng tôi có thể nhận Grid_Clickđầu vào.

private async void ToolBtn_OnClick(object sender, RoutedEventArgs e)
{
    var senderBtn = sender as Button;
    senderBtn.IsEnabled = false;

    var response = await Utility.PickPoint(myGrid);
    MessageBox.Show(response.ToString());
    senderBtn.IsEnabled = true;
}  

public static class Utility
{
    private static TaskCompletionSource<bool> tcs;
    private static Point _point = new Point();

    public static async Task<Point> PickPoint(Grid grid)
    {
        tcs = new TaskCompletionSource<bool>();
        _point = new Point();

        grid.MouseLeftButtonUp += GridOnMouseLeftButtonUp;


        await tcs.Task;

        grid.MouseLeftButtonUp -= GridOnMouseLeftButtonUp;
        return _point;
    }


    private static void GridOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        // do something here ....
        _point = new Point { X = 23, Y = 34 };
        // do something here ....

        tcs.SetResult(true); // as soon its set it will go back

    }
}

@thanks, đây là cùng một câu trả lời tôi nhận được cho câu hỏi khác của tôi sử dụng async / await.
Vahid

ồ vâng Tôi nhận thấy nó bây giờ nhưng tôi đoán đó là cách duy nhất tôi thấy nó hoạt động
Rao Hammas Hussain

2

Bạn có thể chặn không đồng bộ bằng cách sử dụng SemaphoreSlim:

public partial class MainWindow : Window, IDisposable
{
    private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var point = Utility.PickPoint(View);

        // do not continue the code flow until the user has clicked on the grid. 
        // so when we debug, the code flow will literally stop here.
        await _semaphoreSlim.WaitAsync();

        MessageBox.Show(point.ToString());
    }

    private void View_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        //click on grid detected....
        _semaphoreSlim.Release();
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        Dispose();
    }

    public void Dispose() => _semaphoreSlim.Dispose();
}

Bạn không thể, và bạn cũng không muốn chặn luồng xử lý đồng bộ bởi vì sau đó nó sẽ không bao giờ có thể xử lý nhấp chuột vào Grid, tức là nó không thể bị chặn và xử lý các sự kiện cùng một lúc.


Cảm ơn cho một câu trả lời thay thế. Tôi tự hỏi làm thế nào nó được thực hiện ví dụ trong Console.Readline ()? Khi bạn tiếp cận phương thức này trong trình gỡ lỗi, nó sẽ dừng ở đó một cách kỳ diệu trừ khi chúng ta nhập một cái gì đó? Có khác về cơ bản trong các ứng dụng Console không? Chúng ta không thể có hành vi tương tự trong ứng dụng WinForms / WPF? Tôi đã thấy điều này trong API của Autodesk Revit, có một phương thức PickPoint () ở đó buộc bạn phải chọn một điểm trên màn hình và tôi chưa thấy bất kỳ async / await nào được sử dụng! Có phải ít nhất là có thể ẩn từ khóa đang chờ và gọi nó bằng cách nào đó từ một phương thức đồng bộ hóa?
Vahid

@Vahid: Console.Readline chặn , tức là nó không quay lại cho đến khi một dòng được đọc. PickPointPhương pháp của bạn không. Nó trở lại ngay lập tức. Nó có khả năng chặn nhưng sau đó bạn sẽ không thể xử lý đầu vào UI trong thời gian đó như tôi đã viết trong câu trả lời của mình. Nói cách khác, bạn sẽ phải xử lý nhấp chuột bên trong phương thức để có hành vi tương tự.
mm8

Các khối Console.Realine () nhưng đồng thời các sự kiện KeyPress được cho phép. Chúng ta không thể có hành vi tương tự chính xác ở đây? Chặn bằng PickPoint () và chỉ cho phép MouseEvents? Tôi không thể hiểu tại sao có thể trong Bảng điều khiển nhưng không phải trên ứng dụng dựa trên giao diện người dùng.
Vahid

Sau đó, bạn cần thiết lập một bộ điều phối riêng trong PickPointđó xử lý các sự kiện chuột. Tôi không thấy bạn đang đi đâu với điều này?
mm8

1
@Vahind: Tạo mã không đồng bộ và để người dùng chờ phương thức sau đó? Đây là API tôi mong đợi với tư cách là nhà phát triển UI. Gọi một phương thức chặn trong ứng dụng UI không có ý nghĩa gì.
mm8

2

Về mặt kỹ thuật có thể có AutoResetEventvà không có async/await, nhưng có một nhược điểm đáng kể:

public static Point PickPoint(Grid grid)
{
    var pointPicked = new AutoResetEvent(false);
    grid.MouseLeftButtonUp += (s, e) => 
    {
        // do whatever after the grid is clicked

        // signal the end of waiting
        pointPicked.Set();
    };

    // code flow will stop here and wait until the grid is clicked
    pointPicked.WaitOne();
    // return something...
}

Hạn chế: Nếu bạn gọi phương thức này trực tiếp trong trình xử lý sự kiện nút như mã mẫu của bạn, thì việc bế tắc sẽ xảy ra và bạn sẽ thấy ứng dụng dừng đáp ứng. Vì bạn đang sử dụng luồng UI duy nhất để chờ nhấp chuột của người dùng, nên nó không thể phản hồi bất kỳ hành động nào của người dùng, kể cả nhấp chuột của người dùng trên lưới.

Người tiêu dùng của phương pháp nên gọi nó trong một luồng khác để ngăn chặn bế tắc. Nếu nó có thể được đảm bảo, điều đó tốt. Nếu không, bạn cần gọi phương thức như thế này:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    // here I used ThreadPool, but you may use other means to run on another thread
    ThreadPool.QueueUserWorkItem(new WaitCallback(Capture));
}

private void Capture(object state)
{
    // do not continue the code flow until the user has clicked on the grid. 
    // so when we debug, the code flow will literally stop here.
    var point = Utility.PickPoint(View);


    MessageBox.Show(point.ToString());
}

Nó có thể gây ra nhiều rắc rối hơn cho người tiêu dùng API của bạn ngoại trừ họ đã sử dụng để quản lý các luồng của riêng họ. Đó là lý do tại sao async/awaitđược phát minh.


Cảm ơn Ken, có thể là addin bắt đầu từ một luồng khác và sau đó các sự kiện của nó không chặn luồng UI chính?
Vahid

@Vahid Có và không. Có, bạn có thể gọi phương thức chặn trong luồng khác và bọc nó trong phương thức khác. Tuy nhiên, phương thức trình bao bọc vẫn cần được gọi trong một luồng khác ngoài luồng UI để tránh chặn UI. Bởi vì trình bao bọc sẽ chặn luồng cuộc gọi nếu nó đồng bộ . Mặc dù bên trong trình bao bọc chặn một luồng khác, nó vẫn cần đợi kết quả và chặn luồng gọi. Nếu người gọi gọi phương thức trình bao bọc trong luồng UI, UI sẽ bị chặn.
Ken Hung

0

Tôi nghĩ rằng vấn đề là trên chính thiết kế. Nếu API của bạn hoạt động trên một phần tử cụ thể thì nó nên được sử dụng trong trình xử lý sự kiện của chính phần tử này, chứ không phải trên phần tử khác.

Ví dụ: ở đây chúng tôi muốn có được vị trí của sự kiện nhấp chuột trên Lưới, API cần được sử dụng trong trình xử lý sự kiện được liên kết với sự kiện trên phần tử Lưới, không phải trên phần tử nút.

Bây giờ, nếu yêu cầu là chỉ xử lý nhấp chuột vào Lưới sau khi chúng tôi đã nhấp vào Nút, thì trách nhiệm của Nút sẽ là thêm trình xử lý sự kiện trên Lưới và sự kiện nhấp vào Lưới sẽ hiển thị hộp thông báo và xóa trình xử lý sự kiện này được thêm vào bằng nút để nó không kích hoạt nữa sau lần nhấp này ... (không cần chặn Chủ đề giao diện người dùng)

Chỉ cần nói rằng nếu bạn chặn luồng UI khi nhấp vào nút, tôi không nghĩ rằng luồng UI sẽ có thể kích hoạt sự kiện nhấp chuột trên Lưới sau đó.


0

Trước hết, chủ đề UI không thể bị chặn giống như câu trả lời bạn đã nhận được từ câu hỏi ban đầu của mình.
Nếu bạn có thể đồng ý với điều đó, thì việc tránh async / await để khiến khách hàng của bạn thực hiện ít sửa đổi hơn là có thể thực hiện được và thậm chí không cần bất kỳ đa luồng nào.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Utility.PickPoint(View, (x) => MessageBox.Show(x.ToString()));
    }
}

public static class Utility
{
    private static Action<Point> work;

    public static void PickPoint(Grid grid, Action<Point> work)
    {
        if (Utility.work == null)
        {
            grid.PreviewMouseLeftButtonUp += Grid_PreviewMouseLeftButtonUp;
            Utility.work = work;
        }
    }

    private static void Grid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;
        work.Invoke(e.GetPosition(grid));
        grid.PreviewMouseLeftButtonUp -= Grid_PreviewMouseLeftButtonUp;
        Utility.work = null;
    }
}   

Nhưng nếu bạn muốn chặn luồng UI hoặc "dòng mã", câu trả lời sẽ là không thể. Bởi vì nếu luồng UI bị chặn, không thể nhận thêm đầu vào.
Vì bạn đã đề cập đến ứng dụng console, tôi chỉ giải thích đơn giản.
Khi bạn chạy ứng dụng bảng điều khiển hoặc gọi AllocConsoletừ một quy trình không gắn vào bất kỳ bảng điều khiển nào (cửa sổ), conhost.exe có thể cung cấp bảng điều khiển (cửa sổ) sẽ được thực thi và quy trình ứng dụng hoặc trình điều khiển của người gọi sẽ được gắn vào bảng điều khiển ( cửa sổ).
Vì vậy, bất kỳ mã nào bạn viết có thể chặn luồng của người gọi, chẳng hạn như Console.ReadKeysẽ chặn luồng UI của cửa sổ bàn điều khiển, đó là lý do tại sao khi ứng dụng bảng điều khiển chờ đầu vào của bạn nhưng vẫn có thể phản hồi với đầu vào khác như nhấp chuột.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.