如何在C#中触发事件之前阻止代码流


9

在这里,我们有Grid一个Button。当用户单击按钮时,将执行Utility类中的方法,该方法强制应用程序接收对Grid的单击。代码流必须在此处停止,并且直到用户单击时才能继续Grid

在此之前,我有一个类似的问题:

等到用户单击C#WPF

在这个问题中,我使用了有效的async / await得到了答案,但是由于我打算将其用作API的一部分,因此我不想使用async / await,因为消费者随后必须用我不想要的异步。

如何编写Utility.PickPoint(Grid grid)方法以实现此目标?

我看到这可能有所帮助,但说实话,我对此并不完全了解:

阻塞直到事件完成

将其视为类似于Console应用程序中的Console.ReadKey()方法的东西。当我们调用此方法时,代码流将停止,直到我们输入一些值为止。在我们输入内容之前,调试器不会继续。我想要PickPoint()方法的确切行为。代码流将停止,直到用户单击网格为止。

<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)
    {

    }
}

显而易见的方法是Aync/Await如何执行操作A并保存该操作STATE,现在您希望该用户单击Grid ..so,如果用户单击Grid,则检查状态是否为true,然后执行其他操作即可?
Rao Hammas Hussain

@RaoHammasHussain我用一个可能有用的链接更新了我的问题。实用程序方法将是API的一部分,只要用户希望请求最终用户单击屏幕,API的用户就会调用该方法。将其视为普通Windows应用程序或Console.Readline()方法中的文本提示窗口。在这些情况下,代码流将停止,直到用户输入内容为止。现在,我想要确切的东西,但是这次用户单击屏幕。
Vahid

AutoResetEvent不是你想要的吗?
Rao Hammas Hussain

@RaoHammasHussain我这么认为,但真的不知道如何在这里使用它。
Vahid

就像您有意实施WAIT STATE。真的需要吗?因为您不能将其var point = Utility.PickPoint(Grid grid);放在Grid Click方法中吗?进行一些操作并返回响应?
Rao Hammas Hussain

Answers:


7

“如何在事件触发之前阻止代码流?”

您的做法是错误的。事件驱动并不意味着阻止并等待事件。您从不等待,至少您总是尽力避免这种情况。等待正在浪费资源,阻塞线程,并可能带来死锁或僵尸线程的风险(以防永不发出信号)。
应该很清楚,阻塞线程等待事件是一种反模式,因为它与事件的思想相矛盾。

通常,您有两个(现代)选项:实现异步API或事件驱动的API。由于您不想异步实现API,因此需要使用事件驱动的API。

事件驱动的API的关键在于,您可以让调用者继续并在结果准备好或操作完成后向他发送通知,而不是强制调用者同步等待结果或轮询结果。同时,呼叫者可以继续执行其他操作。

从线程的角度看问题时,事件驱动的API允许调用线程(例如执行按钮的事件处理程序的UI线程)自由地继续处理与UI相关的操作,例如呈现UI元素或处理用户输入,例如鼠标移动和按键。尽管不方便,但效果类似于异步API。

由于您没有提供足够的详细信息来说明您Utility.PickPoint()实际上要做什么,实际正在做什么以及任务的结果是什么,或者用户为什么必须单击“网格”,所以我无法为您提供更好的解决方案。我只是可以提供一种如何实现您的要求的一般模式。

您的流程或目标显然分为至少两个步骤,以使其成为一系列操作:

  1. 当用户单击按钮时执行操作1
  2. 当用户单击“执行”时,执行操作2(继续/完成操作1)。 Grid

至少有两个约束:

  1. 可选:必须在允许API客户端重复序列之前完成序列。操作2运行完成后,序列即完成。
  2. 始终在操作2之前执行操作1。操作1启动序列。
  3. 必须在允许API客户端执行操作2之前完成操作1

这要求API的客户端收到两个通知,以允许非阻塞交互:

  1. 操作1已完成(或需要互动)
  2. 操作2(或目标)已完成

您应该通过公开两个公共方法和两个公共事件,让您的API实现此行为和约束。

实施/重构实用程序API

Utility.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));
  }
}

PickPointCompletedEventArgs.cs

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

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

使用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>

备注

在后台线程上引发的事件将在同一线程上执行其处理程序。DispatcherObject从在后台线程上执行的处理程序访问类似UI元素的操作,需要将关键操作加入或Dispatcher使用,以避免交叉线程异常。Dispatcher.InvokeDispatcher.InvokeAsync


一些想法-回复您的评论

因为您正在与我一起寻找一种“更好的”阻止解决方案,以控制台应用程序为例,所以我说服您,您的看法或观点是完全错误的。

“考虑其中包含这两行代码的控制台应用程序。

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

在调试模式下执行应用程序时会发生什么。它将在代码的第一行停止,并强制您在控制台UI中输入一个值,然后在输入内容并按Enter之后,它将执行下一行并实际打印您输入的内容。我在考虑完全相同的行为,只是在WPF应用程序中。”

控制台应用程序完全不同。线程概念不同。控制台应用程序没有GUI。只是输入/输出/错误流。您无法将控制台应用程序的体系结构与丰富的GUI应用程序进行比较。这行不通。您确实必须理解并接受这一点。

WPF是围绕渲染线程和UI线程构建的。这些线程始终保持旋转状态,以便与OS进行通信,例如处理用户输入-保持应用程序响应速度。您永远不想暂停/阻止此线程,因为它将阻止框架执行必要的后台工作(例如响应鼠标事件-您不希望鼠标冻结):

等待=线程阻塞=无响应=不良UX =烦恼的用户/客户=办公室出现麻烦。

有时,应用程序流程需要等待输入或例程完成。但是我们不想阻塞主线程。
这就是为什么人们发明了复杂的异步编程模型的原因,以允许在不阻塞主线程的情况下进行等待,而又不强迫开发人员编写复杂且错误的多线程代码。

每个现代应用程序框架都提供异步操作或异步编程模型,以允许开发简单而有效的代码。

您正在努力抵制异步编程模型这一事实表明,我有些缺乏理解。每个现代开发人员都喜欢异步API,而不是同步API。没有认真的开发人员会在乎使用await关键字或声明其方法async。没有人。您是我遇到的第一个抱怨异步API的人,他们发现它们不方便使用。

如果我要检查您的框架,该框架的目标是解决与UI相关的问题或使与UI相关的任务更加容易,那么我希望它一直都是异步的。
与UI相关的非异步API是浪费的,因为这会使我的编程风格复杂化,因此我的代码因此更容易出错并且难以维护。

一个不同的观点:当您确认等待阻塞了UI线程时,由于UI将冻结直到等待结束,因此会产生非常糟糕的用户体验,现在您意识到了这一点,为什么要提供鼓励使用API​​或插件模型的原因开发人员完全可以做到这一点-实施等待吗?
您不知道3rd party插件将做什么以及例程完成之前需要花费多长时间。这只是一个糟糕的API设计。当您的API在UI线程上运行时,API的调用者必须能够对其进行非阻塞调用。

如果您拒绝唯一便宜或优雅的解决方案,而不是使用示例所示的事件驱动方法。
它可以满足您的要求:启动例程-等待用户输入-继续执行-完成目标。

我确实尝试过几次以解释为什么等待/阻止是一个不好的应用程序设计。同样,您不能将控制台UI与丰富的图形UI进行比较,例如,单独的输入处理比仅侦听输入流要复杂得多。我真的不知道您的经验水平和学习起点,但是您应该开始接受异步编程模型。我不知道您尝试避免这种情况的原因。但这根本不明智。

如今,异步编程模型在每个平台,编译器,每个环境,浏览器,服务器,桌面,数据库上的任何地方都可以实现。事件驱动模型可以实现相同的目标,但是依赖于后台线程,使用起来较不方便(订阅/取消订阅事件/从事件中订阅)。事件驱动是老式的,仅当异步库不可用或不适用时才应使用。

“我已经看到了Autodesk Revit的确切行为。”

行为(您所体验或观察到的)与该体验的实施方式有很大不同。两件事。您的Autodesk很可能使用异步库或语言功能或某些其他线程机制。它也是与上下文相关的。当您想到的方法在后台线程上执行时,开发人员可以选择阻止该线程。他要么有充分的理由这样做,要么只是做出了错误的设计选择。您完全走错了路;)阻塞不好。
(Autodesk源代码是开源的吗?还是您怎么知道其实现方式?)

我不想得罪你,请相信我。但请重新考虑实现您的API异步。开发人员不喜欢使用async / await,这只是在您的脑海中。您显然有错误的心态。忘了那个控制台应用程序的参数-这是胡说八道;)

与UI相关的API 必须 尽可能使用async / await。否则,将所有工作留给API的客户端编写非阻塞代码。您会迫使我将对API的每次调用都包装到后台线程中。或者使用不太舒适的事件处理。相信我-每个开发人员都宁愿用来装饰其成员async,而不是进行事件处理。每次使用事件时,您可能会冒潜在的内存泄漏的风险-取决于某些情况,但是这种风险是真实的,并且在不小心编程时并不罕见。

我真的希望你能理解为什么阻塞不好。我真的希望您决定使用async / await编写现代异步API。不过,尽管我敦促您使用异步/等待,但我向您展示了一种使用事件等待非阻塞的非常普通的方法。

“ API将允许程序员访问UI等。现在,假定程序员希望开发一个外接程序,当单击按钮时,将要求最终用户在UI中选择一个点。”

如果您不想让插件直接访问UI元素,则应提供一个接口来委托事件或通过抽象对象公开内部组件。
API内部将代表外接程序预订UI事件,然后通过向API客户端公开相应的“包装器”事件来委托事件。您的API必须提供一些挂钩,外接程序可以在其中连接以访问特定的应用程序组件。插件API的作用类似于适配器或Facade,以使外部对象可以访问内部对象。
允许一定程度的隔离。

看一下Visual Studio如何管理插件或允许我们实现它们。假设您想为Visual Studio编写一个插件,并对如何执行此操作进行一些研究。您将认识到Visual Studio通过接口或API公开了其内部。EG,您可以操纵代码编辑器或获取有关编辑器内容的信息,而无需真正访问它。


嗨,谢谢您从另一个角度解决这个问题。抱歉,该问题在细节上有点含糊。考虑其中包含这两行代码的控制台应用程序。var str = Console.ReadLine(); Console.WriteLine(str);在调试模式下执行应用程序时会发生什么。它将在代码的第一行停止,并强制您在控制台UI中输入一个值,然后在输入内容并按Enter后,它将执行下一行并实际打印您输入的内容。我在考虑完全相同的行为,但是在WPF应用程序中。
Vahid

在我正在开发的CAD应用程序中,用户应该能够通过插件/插件对其进行扩展。该API将允许程序员访问UI等。现在,假定程序员想要开发一个插件,即当单击按钮时,要求最终用户在UI中选择一个点,然后代码将执行其他操作。给定点很酷的东西。也许他们会要求被拾起另一点和画线等
瓦希德

我已经在Autodesk Revit中看到了确切的行为。
Vahid

1
关于您的要求,我有话要说。请阅读我更新的答案。我把回复贴在那里,因为它花了更长的时间。我承认你真的触发了我。请阅读时,请记住,我不想得罪你。
BionicCode

感谢您更新的答案。当然,我并不生气。相反,我非常感谢您为此付出的时间和精力。
Vahid

5

我个人认为这让每个人都变得过于复杂,但是也许我还没有完全理解为什么需要以某种方式完成此操作的原因,但是似乎可以在此处使用简单的布尔检查。

首先,通过设置BackgroundIsHitTestVisible属性使您的网格可测试,否则它甚至无法捕获鼠标单击。

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

接下来,创建一个bool值,该值可以存储是否应发生“ GridClick”事件。单击网格后,请检查该值并从网格单击事件中执行是否正在等待单击。

例:

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
}

大家好,Tronald,我想您误解了这个问题。我需要的是代码在Utility.PickPoint(View)处停止并仅在用户单击Grid之后才继续。
Vahid

哦,是的,我完全误会了。抱歉,我没有意识到您实际上需要停止一切。我认为没有多线程是不可能的,因为整个UI都将被阻塞。
Tronald

我仍然不确定是否不可能。使用async / await绝对有可能,这不是一个多线程解决方案。但是我需要的是异步/等待解决方案的替代方案。
Vahid

1
可以,但是您提到不能使用异步/等待。似乎您需要利用一个调度程序和一个与主线程(在UI上执行)分离的线程。我希望您能以我感兴趣的方式找到另一种方式
Tronald

2

我尝试了几件事,但是没有它我无法做到async/await。因为如果不使用它,会导致DeadLockUI阻塞或阻塞,然后我们就可以接受Grid_Click输入。

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

    }
}

@谢谢,这是我对使用异步/等待的其他问题的相同答案。
Vahid

哦,是的!我现在注意到了,但我想这是我发现
可行

2

您可以使用异步阻止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();
}

您不能(也不想)同步地阻止调度程序线程,因为那样它将永远无法处理对的单击Grid,即不能同时阻止和处理事件。


感谢您提供替代答案。我想知道如何在Console.Readline()中完成它吗?当您在调试器中达到此方法时,除非我们输入某些内容,否则它会神奇地停在那里。控制台应用程序中是否有根本不同?我们在WinForms / WPF应用程序中不能有相同的行为吗?我已经在Autodesk Revit的API中看到了这一点,那里有一个PickPoint()方法可以强迫您在屏幕上选择一个点,而且我还没有看到使用任何异步/等待!至少可以隐藏await关键字,然后通过sync方法以某种方式调用它吗?
Vahid

@Vahid:Console.Readline blocks,即直到读取一行才返回。您的PickPoint方法没有。它立即返回。它可能会阻塞,但是在此期间,您将无法处理UI输入,如我在回答中所写。换句话说,您必须处理方法内部的单击才能获得相同的行为。
mm8

Console.Realine()会阻止,但同时允许KeyPress事件。我们在这里不能有完全相同的行为吗?由PickPoint()阻止并仅允许MouseEvents?我无法理解为什么可以在Console中但不能在基于UI的应用程序中做到这一点。
Vahid

然后,您需要在其中设置一个单独的调度程序PickPoint来处理鼠标事件。我看不到你要去哪里?
mm8

1
@Vahind:使代码异步,然后让用户等待该方法吗?这是我希望作为UI开发人员使用的API。在UI应用程序中调用阻塞方法没有任何意义。
mm8

2

从技术上讲,使用AutoResetEvent和不使用都可以async/await,但是存在一个明显的缺点:

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...
}

缺点:如果像示例代码一样直接在按钮事件处理程序中调用此方法,则会发生死锁,并且您将看到应用程序停止响应。因为您使用的是唯一的UI线程来等待用户的单击,所以它无法响应任何用户的操作,包括用户在网格上的单击。

该方法的使用者应在另一个线程中调用它以防止死锁。如果可以保证,那很好。否则,您需要调用如下方法:

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());
}

这可能会给您的API使用者带来更多麻烦,除非他们曾经用来管理自己的线程。这就是为什么async/await发明。


谢谢Ken,外接程序是否可能从另一个线程开始,然后其事件不会阻塞主UI线程?
Vahid

@Vahid是和否。是的,您可以在另一个线程中调用阻塞方法,然后将其包装在另一个方法中。但是,仍然需要在UI线程以外的其他线程中调用wrapper方法,以避免UI阻塞。因为如果包装程序是同步的,包装程序将阻塞调用线程。尽管包装器在内部阻止了另一个线程,但它仍需要等待结果并阻止调用线程。如果调用方在UI线程中调用wrapper方法,则UI被阻止。

0

我认为问题在于设计本身。如果您的API适用于特定元素,则应在该元素的事件处理程序中使用它,而不要在另一个元素上使用它。

例如,在这里我们要获取click事件在Grid上的位置,需要在与Grid元素而不是button元素上的事件关联的事件处理程序中使用API​​。

现在,如果要求是仅在单击按钮之后才处理对网格的单击,那么按钮的责任将是在网格上添加事件处理程序,并且网格上的click事件将显示消息框并删除该事件处理程序由按钮添加,因此单击此按钮后将不再触发...(无需阻止UI线程)

只是说,如果您在按钮单击时阻止了UI线程,我认为UI线程之后将无法触发Grid上的click事件。


0

首先,UI线程无法像您从早期问题中得到的答案一样被阻塞。
如果您同意这一点,那么避免异步/等待使您的客户做更少的修改是可行的,甚至不需要任何多线程。

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;
    }
}   

但是,如果要阻止UI线程或“代码流”,答案将是不可能的。因为如果UI线程被阻止,则无法再接收任何输入。
既然您提到控制台应用程序,我只做一些简单的解释。
当您运行控制台应用程序或AllocConsole从未附加到任何控制台(窗口)的进程进行调用时,将执行可提供控制台(窗口)的conhost.exe,并将控制台应用程序或调用者进程附加到控制台(窗口)。
因此,您编写的任何可能阻止调用者线程的代码Console.ReadKey都不会阻止控制台窗口的UI线程,这就是控制台应用程序等待您的输入但仍可以响应其他输入(例如鼠标单击)的原因。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.