在我的C#/ XAML Metro应用,有揭开序幕长期运行过程中的按钮。 因此,推荐,我使用异步/等待以确保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
// (e.g. contact some web services)
...
}
偶尔,东西GetResults内发生的事情将需要更多的用户输入才能继续。 为简单起见,假设用户只是必须点击“继续”按钮。
我的问题是: 我怎么能暂停GetResults的执行,它等待的事件 ,如另一个按钮的点击这样的方式?
下面是一个丑陋的方式来实现我正在寻找:在继续”按钮事件处理程序设置一个标志...
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;
轮询显然是可怕的(周期忙等待/废物)和我正在寻找基于事件的东西。
有任何想法吗?
顺便说一下在该简化示例中,一种解决方案将是当然的分裂GetResults()分为两个部分,调用第一部分从一开始按钮,所述第二部分从所述继续按钮。 在现实中,东西在GetResults发生的事情比较复杂,不同类型的用户输入可以在执行中的不同点是必需的。 所以,打破了逻辑分成多个方法将是不平凡的。
Answer 1:
您可以使用的实例SemaphoreSlim类作为一个信号:
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
// set signal in event
signal.Release();
// wait for signal somewhere else
await signal.WaitAsync();
可替换地,可以使用的实例TaskCompletionSource <T>类来创建任务<T>表示所述按钮点击的结果:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// complete task in event
tcs.SetResult(true);
// wait for task somewhere else
await tcs.Task;
Answer 2:
当你有你需要的不寻常的事情await
上,最简单的答案往往是TaskCompletionSource
(或async
基于原始-启用TaskCompletionSource
)。
在这种情况下,您的需要是很简单的,所以你可以只使用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
// (e.g. 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
) 。
Answer 3:
下面是我用一个实用工具类:
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;
Answer 4:
理想情况下,你不知道 。 虽然你当然可以阻止异步线程,这是资源的浪费,而且不理想。
考虑用户去吃午饭,而按钮等待被点击的典型例子。
如果您已经暂停您的异步代码,同时等待来自用户的输入,那么它只是浪费资源,同时该线程暂停。
这就是说,它的更好,如果你的异步操作,设置,你需要维持该点的按钮被激活,其中国家和你在点击“等待”。 在这一点上,你GetResults
方法停止 。
然后 ,单击该按钮时,根据您所储存的状态,启动另一个异步任务 ,继续工作。
由于SynchronizationContext
将调用的事件处理程序捕获GetResults
(编译器会做,因为使用的结果await
关键词被使用,而事实上, SynchronizationContext.Current应该非空,鉴于你是在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
// (e.g. contact some web services)
...
}
ContinueToGetResultsAsync
是继续得到您的按钮被按下的事件结果的方法。 如果您的按钮不推,那么你的事件处理程序不执行任何操作。
Answer 5:
斯蒂芬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;
}
}
}
Answer 6:
简单的辅助类:
public class EventAwaiter<TEventArgs>
{
private readonly TaskCompletionSource<TEventArgs> _eventArrived = new TaskCompletionSource<TEventArgs>();
private readonly Action<EventHandler<TEventArgs>> _unsubscribe;
public EventAwaiter(Action<EventHandler<TEventArgs>> subscribe, Action<EventHandler<TEventArgs>> unsubscribe)
{
subscribe(Subscription);
_unsubscribe = unsubscribe;
}
public Task<TEventArgs> Task => _eventArrived.Task;
private EventHandler<TEventArgs> Subscription => (s, e) =>
{
_eventArrived.TrySetResult(e);
_unsubscribe(Subscription);
};
}
用法:
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
Answer 7:
随着反应扩展(Rx.Net)
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
var res = await eventObservable.FirstAsync();
你可以用NuGet包System.Reactive添加的Rx
测试的样品:
private static event EventHandler<EventArgs> _testEvent;
private static async Task Main()
{
var eventObservable = Observable
.FromEventPattern<EventArgs>(
h => _testEvent += h,
h => _testEvent -= h);
Task.Delay(5000).ContinueWith(_ => _testEvent?.Invoke(null, new EventArgs()));
var res = await eventObservable.FirstAsync();
Console.WriteLine("Event got fired");
}
文章来源: Is it possible to await an event instead of another async method?