是否有可能等待一个事件,而不是另一个asynchronous方法?

在我的C#/ XAML metro应用程序中,有一个button可以启动长时间运行的程序。 所以,build议,我使用asynchronous/等待,以确保UI线程不会被阻止:

private async void Button_Click_1(object sender, RoutedEventArgs e) { await GetResults(); } private async Task GetResults() { // Do lot of complex stuff that takes a long time // (eg contact some web services) ... } 

偶尔,GetResults内发生的事情需要额外的用户input才能继续。 为了简单起见,假设用户只需点击一个“继续”button。

我的问题是: 如何暂停执行GetResults,以等待事件 ,如点击另一个button?

这是一个丑陋的方式来实现我正在寻找:继续的事件处理程序“button设置一个标志…

 private bool _continue = false; private void buttonContinue_Click(object sender, RoutedEventArgs e) { _continue = true; } 

…和GetResults定期轮询它:

  buttonContinue.Visibility = Visibility.Visible; while (!_continue) await Task.Delay(100); // poll _continue every 100ms buttonContinue.Visibility = Visibility.Collapsed; 

投票显然是可怕的(忙等待/浪费周期),我正在寻找事件为基础。

有任何想法吗?

顺便说一句,在这个简单的例子中,一个解决scheme当然是将GetResults()分成两部分,从开始button调用第一部分,从继续button调用第二部分。 实际上,GetResults中发生的事情比较复杂,在执行过程中的不同点可能需要不同types的用户input。 所以将逻辑分解为多种方法将是不平凡的。

您可以使用SemaphoreSlim类的实例作为信号:

 private SemaphoreSlim signal = new SemaphoreSlim(0, 1); // set signal in event signal.Release(); // wait for signal somewhere else await signal.WaitAsync(); 

或者,您可以使用TaskCompletionSource <T>类的实例来创build代表button单击的结果的Task <T> :

 private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); // complete task in event tcs.SetResult(true); // wait for task somewhere else await tcs.Task; 

当你有一个不寻常的事情需要await时,最简单的答案通常是TaskCompletionSource (或者一些基于TaskCompletionSource async原语)。

在这种情况下,您的需求非常简单,所以您可以直接使用TaskCompletionSource

 private TaskCompletionSource<object> continueClicked; private async void Button_Click_1(object sender, RoutedEventArgs e) { // Note: You probably want to disable this button while "in progress" so the // user can't click it twice. await GetResults(); // And re-enable the button here, possibly in a finally block. } private async Task GetResults() { // Do lot of complex stuff that takes a long time // (eg contact some web services) // Wait for the user to click Continue. continueClicked = new TaskCompletionSource<object>(); buttonContinue.Visibility = Visibility.Visible; await continueClicked.Task; buttonContinue.Visibility = Visibility.Collapsed; // More work... } private void buttonContinue_Click(object sender, RoutedEventArgs e) { if (continueClicked != null) continueClicked.TrySetResult(null); } 

从逻辑上讲, TaskCompletionSource就像一个async ManualResetEvent ,只不过你只能“设置”一次事件,而事件可以有一个“结果”(在这种情况下,我们没有使用它,所以我们只是将结果设置为null ) 。

理想情况下,你不这样做 。 虽然你当然可以阻止asynchronous线程,但这是浪费资源,而不是理想的。

考虑用户在button等待点击时进入午餐的典型示例。

如果在等待来自用户的input时停止了asynchronous代码,那么只是在该线程暂停时浪费资源。

也就是说,最好是在asynchronous操作中,将需要维护的状态设置为启用该button的位置,并且您正在等待点击。 此时,您的GetResults方法停止

然后,单击button时,根据您已存储的状态,启动另一个asynchronous任务以继续工作。

由于SynchronizationContext将在调用GetResults的事件处理程序中捕获(编译器将使用正在使用的await关键字的结果,并且SynchronizationContext.Current应该为非null,因为您处于UI应用程序中),你可以像这样使用async / await

 private async void Button_Click_1(object sender, RoutedEventArgs e) { await GetResults(); // Show dialog/UI element. This code has been marshaled // back to the UI thread because the SynchronizationContext // was captured behind the scenes when // await was called on the previous line. ... // Check continue, if true, then continue with another async task. if (_continue) await ContinueToGetResultsAsync(); } private bool _continue = false; private void buttonContinue_Click(object sender, RoutedEventArgs e) { _continue = true; } private async Task GetResults() { // Do lot of complex stuff that takes a long time // (eg contact some web services) ... } 

ContinueToGetResultsAsync是在按下button的情况下继续获取结果的方法。 如果你的button没有按下,那么你的事件处理程序什么都不做。

Stephen Toub 在他的博客上发布了这个AsyncManualResetEvent类。

 public class AsyncManualResetEvent { private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>(); public Task WaitAsync() { return m_tcs.Task; } public void Set() { var tcs = m_tcs; Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default); tcs.Task.Wait(); } public void Reset() { while (true) { var tcs = m_tcs; if (!tcs.Task.IsCompleted || Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs) return; } } } 

这是我使用的一个实用程序类:

 public class AsyncEventListener { private readonly Func<bool> _predicate; public AsyncEventListener() : this(() => true) { } public AsyncEventListener(Func<bool> predicate) { _predicate = predicate; Successfully = new Task(() => { }); } public void Listen(object sender, EventArgs eventArgs) { if (!Successfully.IsCompleted && _predicate.Invoke()) { Successfully.RunSynchronously(); } } public Task Successfully { get; } } 

以下是我如何使用它:

 var itChanged = new AsyncEventListener(); someObject.PropertyChanged += itChanged.Listen; // ... make it change ... await itChanged.Successfully; someObject.PropertyChanged -= itChanged.Listen; 

简单的帮手类:

 public class EventAwaiter<TEventArgs> { #region Fields private TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>(); #endregion Fields #region Properties public Task<TEventArgs> Task { get; set; } public EventHandler<TEventArgs> Subscription => (s, e) => _eventArrived.TrySetResult(e); #endregion Properties } 

用法:

 var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(); example.YourEvent += valueChangedEventAwaiter.Subscription; await valueChangedEventAwaiter.Task;