Max*_*Max 146 c# async-await microsoft-metro .net-4.5 windows-store-apps
在我的C#/ XAML metro应用程序中,有一个启动长时间运行过程的按钮.所以,按照建议,我使用async/await来确保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)
...
}
Run Code Online (Sandbox Code Playgroud)
偶尔,GetResults内发生的事情需要额外的用户输入才能继续.为简单起见,假设用户只需单击"继续"按钮即可.
我的问题是:如何以等待点击另一个按钮等事件的方式暂停GetResults的执行?
这是实现我正在寻找的东西的一种丑陋方式:"继续"按钮的事件处理程序设置了一个标志......
private bool _continue = false;
private void buttonContinue_Click(object sender, RoutedEventArgs e)
{
_continue = true;
}
Run Code Online (Sandbox Code Playgroud)
...和GetResults定期轮询它:
buttonContinue.Visibility = Visibility.Visible;
while (!_continue) await Task.Delay(100); // poll _continue every 100ms
buttonContinue.Visibility = Visibility.Collapsed;
Run Code Online (Sandbox Code Playgroud)
民意调查显然非常糟糕(忙碌的等待/浪费周期),我正在寻找基于事件的东西.
有任何想法吗?
顺便说一下,在这个简化的例子中,一个解决方案当然是将GetResults()分成两部分,从开始按钮调用第一部分,从继续按钮调用第二部分.实际上,GetResults中发生的事情更复杂,并且在执行中的不同点可能需要不同类型的用户输入.因此,将逻辑分解为多种方法将是非常重要的.
dtb*_*dtb 208
您可以使用SemaphoreSlim类的实例作为信号:
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
// set signal in event
signal.Release();
// wait for signal somewhere else
await signal.WaitAsync();
Run Code Online (Sandbox Code Playgroud)
或者,您可以使用TaskCompletionSource <T>类的实例来创建表示按钮单击结果的Task <T>:
private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
// complete task in event
tcs.SetResult(true);
// wait for task somewhere else
await tcs.Task;
Run Code Online (Sandbox Code Playgroud)
Ste*_*ary 68
如果你有一个不寻常的事情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);
}
Run Code Online (Sandbox Code Playgroud)
逻辑上,TaskCompletionSource就像一个async ManualResetEvent,除了你只能"设置"事件一次,事件可以有一个"结果"(在这种情况下,我们没有使用它,所以我们只是设置结果null).
这是我使用的实用程序类:
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; }
}
Run Code Online (Sandbox Code Playgroud)
以下是我如何使用它:
var itChanged = new AsyncEventListener();
someObject.PropertyChanged += itChanged.Listen;
// ... make it change ...
await itChanged.Successfully;
someObject.PropertyChanged -= itChanged.Listen;
Run Code Online (Sandbox Code Playgroud)
理想情况下,你不需要。虽然您当然可以阻止异步线程,但这会浪费资源,而且并不理想。
考虑一个典型的示例,用户在等待单击按钮时去吃午饭。
如果您在等待用户输入时停止了异步代码,那么在该线程暂停时它只是浪费资源。
也就是说,如果在异步操作中,将需要维护的状态设置为启用按钮并且“等待”单击,那就更好了。到那时,你的GetResults方法就停止了。
然后,当单击该按钮时,根据您存储的状态,您可以启动另一个异步任务来继续工作。
因为SynchronizationContext将在调用的事件处理程序中捕获GetResults(编译器将由于使用await正在使用的关键字而执行此操作,并且考虑到您处于 UI 应用程序中, SynchronizationContext.Current应该为非空),因此您可以像这样使用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)
...
}
Run Code Online (Sandbox Code Playgroud)
ContinueToGetResultsAsync是在按下按钮时继续获取结果的方法。如果您的按钮没有被按下,那么您的事件处理程序什么也不做。
简单助手类:
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);
};
}
Run Code Online (Sandbox Code Playgroud)
用法:
var valueChangedEventAwaiter = new EventAwaiter<YourEventArgs>(
h => example.YourEvent += h,
h => example.YourEvent -= h);
await valueChangedEventAwaiter.Task;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
62439 次 |
| 最近记录: |