我在Nutshell中关注了C#的一个例子。根据文本,下面的代码应该是非阻塞的,但我发现在5秒过后,表单才会显示。
private void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => {Thread.Sleep(5000); tcs.SetResult(42); }).Start();
Task<int> task = tcs.Task;
MessageBox.Show(task.Result.ToString());
}
我觉得这与Thread.Sleep()有关,而不是让新线程处于休眠状态,而是让主线程处于休眠状态。
为什么它会阻止UI线程?
答案 0 :(得分:11)
当您尝试获取任务task.Result的结果时,主线程将被阻止,直到任务完成它的执行(即结果可用)。如果您不想等待异步操作完成,请使用task.ContinueWith
:
Task<int> task = tcs.Task;
task.ContinueWith(t => {
MessageBox.Show(t.Result.ToString());
});
顺便说一下,.NET 4.5中有很好的功能可以在任务完成时恢复暂停操作 - 异步方法:
private async void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => { Thread.Sleep(2000); tcs.SetResult(42); }).Start();
int result = await tcs.Task;
MessageBox.Show(result.ToString());
}
此方法将在开始等待任务结果后立即对调用方进行控制。当结果可用时,方法将继续执行并显示消息。
实际上正如@Servy在注释中指出的那样,返回void
的异步方法不是很好的做法(例如,用于错误处理),但有时可以将它们用于事件处理程序。
答案 1 :(得分:5)
当您调用Task.Result.ToString()
时(在MessageBox.Show
中)Task
类有一种机制,在实际给您结果之前等待任务完成(因为它没有实际上有它直到Task
完成。这是我的证据:
private void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => {Thread.Sleep(5000); tcs.SetResult(42); }).Start();
Task<int> task = tcs.Task;
Thread.Sleep(2500);
MessageBox.Show("Waited for 2.5secs on UI thread.");
MessageBox.Show(task.Result.ToString());
}
您会看到它在消息框之前显示的是2.5秒消息框,其中包含42.(事实上,在此之前的2.5秒)。
您正在寻找的是:
private void Form1_Load(object sender, EventArgs e)
{
var tcs = new TaskCompletionSource<int>();
new Thread(() => {Thread.Sleep(5000); tcs.SetResult(42); }).Start();
Task<int> task = tcs.Task;
task.ContinueWith(t => MessageBox.Show(t.Result.ToString()));
}
这将不冻结UI线程。