是否有可能等待一个事件,而不是另一个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;
- 在.NET 4.5中,我可以在C#5中做什么,而在.NET 4中,我无法在C#4中做这些?
- 在entity framework中设置数据库超时
- .NET中高级.NET 4.0和.NET 4.5之间的区别
- 等待运营商只能在一个asynchronous方法内使用
- HttpClient.GetAsync与networking凭据
- 正确的方法来实现一个永无止境的任务。 (定时器vs任务)
- 通过Visual Studio 2010定位.NET Framework 4.5
- .net 4.5是否与.net 4.0并行工作?
- 无法首先将其转换为委托或expression式树types,不能将lambdaexpression式用作dynamic调度操作的参数