6

简而言之,我正在关注 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 线程?

4

2 回答 2

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的异步方法不是很好的做法(例如用于错误处理),但有时可以将它们用于事件处理程序。

于 2013-07-17T17:56:27.553 回答
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());
}

您会看到它在带有 42. 的消息框之前显示了 2.5 秒的消息框。(实际上是在那之前的 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 线程。

于 2013-07-17T17:57:58.727 回答