线程阻塞UI界面

6

我参考了《C# in a Nutshell》中的一个示例。根据文本,以下代码应该是非阻塞的,但我发现窗体要等待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线程?

3
但是您正在在启动任务后立即使用任务的结果,因此它将等待结果准备就绪... - adt
1
你应该使用 Task.Delay 来获取一个在 X 毫秒内完成的任务,而不是使用这种更加繁琐且会创建一个全新的任务却什么也不做(这相当昂贵)的方法。 - Servy
@Servy,Task.Delay()是否也返回一个全新的Task,它只是闲置在那里什么都不做(虽然不在新线程上)? - Frédéric Hamidi
@FrédéricHamidi Task.Delay不需要创建一个新线程来坐着什么都不做。它使用计时器来指示任务应何时完成。这比创建新线程的方式更加可扩展,消耗的资源也少得多。 - Servy
@Servy,所以你的评论都是关于这个线程的,就像我想的一样。我只是想确认一下 :) - Frédéric Hamidi
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的异步方法不是很好的做法(例如用于错误处理),但有时可以在事件处理程序中使用它们。


1
你的 DoSomethingAsync 应该返回一个 Task,只有在绝对必要的情况下才应该使用 async void,而使用 Task.Delay 可以使其实现变得更简单、更容易,并且性能更好。 - Servy
@Servy 我认为OP正在尝试使用TaskCompletionSource。同意返回Task。 - Sergey Berezovskiy

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线程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接