private void button1_Click(object sender, RoutedEventArgs e)
{
string s = LoadString();
textBox1.Text = s;
}
public Task<string> LoadStringAsync();
此方法将非常快速地返回给它的调用者,返回一个 .NET Task<string> 对象,该对象表示异步操作的未来完成及其未来结果。
在未来某个时刻,当操作完成时,任务对象将能够分发操作的结果,在成功加载的情况下可以是字符串,在失败的情况下可以是异常。
无论哪种方式,任务对象都提供了几种机制来通知对象的持有者加载操作已完成。
一种方法是同步阻塞等待任务完成,这可以通过调用任务的 Wait 方法或访问它的 Result 来完成,这将隐式等待直到操作完成......在这两种情况下,在操作完成之前,不会完成对这些成员的调用。
另一种方法是接收异步回调,在那里您向任务注册一个委托,该委托将在任务完成时调用。
这可以使用 Task 的 ContinueWith 方法之一来完成。
使用 ContinueWith,我们现在可以重写之前的 button1_Click 方法,以便在异步等待加载操作完成时不阻塞 UI 线程:
private void button1_Click(object sender, RoutedEventArgs e)
{
Task<string> s = LoadStringAsync();
s.ContinueWith(delegate { textBox1.Text = s.Result; }); // 警告:有问题
}
这实际上是异步启动加载操作,然后在操作完成时异步运行代码以将结果存储到 UI 中。
然而,我们现在有一个新的问题。Windows 窗体、WPF 和 Silverlight 等 UI 框架都对哪些线程能够访问 UI 控件设置了限制,即只能从创建它的线程访问该控件。
然而,在这里,我们在某个任意线程上运行回调来更新 textBox1 的文本,无论 ContinueWith 的任务并行库 (TPL) 实现碰巧放置它。
为了解决这个问题,我们需要一些方法来回到 UI 线程。
不同的 UI 框架为此提供了不同的机制,但在 .NET 中它们都采用基本相同的形式,
private void button1_Click(object sender, RoutedEventArgs e)
{
Task<string> s = LoadStringAsync();
s.ContinueWith(delegate
{
Dispatcher.BeginInvoke(new Action(delegate { textBox1.Text = s.Result; }));
});
}
.NET Framework 进一步抽象了这些返回到 UI 线程的机制,通常是通过 SynchronizationContext 类将一些代码发布到特定上下文的机制。
框架可以通过 SynchronizationContext.Current 属性建立当前上下文,该属性提供表示当前环境的 SynchronizationContext 实例。
此实例的 Post 方法会将委托编组回要调用的此环境:
在 WPF 应用程序中,这意味着将您带回之前所在的调度程序或 UI 线程。
因此,我们可以将之前的代码改写如下:
private void button1_Click(object sender, RoutedEventArgs e)
{
var sc = SynchronizationContext.Current;
Task<string> s = LoadStringAsync();
s.ContinueWith(delegate
{
sc.Post(delegate { textBox1.Text = s.Result; }, null);
});
}
事实上,这种模式非常普遍,.NET 4 中的 TPL 提供了 TaskScheduler.FromCurrentSynchronizationContext() 方法,它允许您使用以下代码做同样的事情:
private void button1_Click(object sender, RoutedEventArgs e)
{
LoadStringAsync().ContinueWith(s => textBox1.Text = s.Result, TaskScheduler.FromCurrentSynchronizationContext());
}
private void button1_Click(object sender, RoutedEventArgs e)
{
string s = await LoadStringAsync();
textBox1.Text = s;
}
static async Task<string> LoadStringAsync()
{
string firstName = await GetFirstNameAsync();
string lastName = await GetLastNameAsync();
return firstName + " " + lastName;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Task<string> s = LoadStringAsync();
textBox1.Text = s.Result; // 警告:有问题
}
private void button1_Click(object sender, RoutedEventArgs e)
{
var mre = new ManualResetEvent(false);
SynchronizationContext.Current.Post(_ => mre.Set(), null);
mre.WaitOne(); // 警告:有问题
}
Task<string> s = LoadStringAsync();
textBox1.Text = s.Result; // BAD ON UI
你可以写成:
Task<string> s = LoadStringAsync();
textBox1.Text = await s; // GOOD ON UI
不要写成下面这样:
Task t = DoWork();
t.Wait(); // BAD ON UI
而是要写成这样:
Task t = DoWork();
await t; // GOOD ON UI

