.Net 4.5和Task.Yield与Task.Delay作为DoEvents的替代方案?使用哪个?

3
我一直在研究在.Net 4.5中引入的async/await语法,并逐渐了解它的用法。
我发现在一些简单的示例中,使用Task.Yield比使用Application.DoEvents更为合适。我尝试了其中一个示例(填充空白)。
Form1上的代码有一个按钮:
public async void button1_Click(object sender, EventArgs e) {
    var list = new List<int>();
    for (int i = 0; i < 10000; i++) {
        list.Add(i);
    }
    for (int i = 0; i < list.Count; i++) {
        Process(list[i]);
        await Task.Yield();
        //await Task.Delay(1);
    }
}

public static void Process(int i) {
    Debug.WriteLine(i);
}

然而,运行此代码时,UI线程被阻塞或者我认为它被阻塞了,因为当代码运行时我无法移动窗口。如果我注释掉Task.Yield()并使用Task.Delay(1)行,则GUI是响应的。
所以我是否在这里误解了什么?我知道使用DoEvents之类的做法是不好的,但我有一些遗留代码需要负责,它使用了这种方式,我打算将其替换为Yield,因为这是最好的选择。但首先我需要熟悉async/await。

在MSDN Library关于Task.Yield的文章中明确指出: 因此,请勿依赖于 await Task.Yield(); 来保持UI响应。 - Hans Passant
好的。在网络上和各种博客中有很多糟糕的样例。 - Wolf5
这太奇怪了。据我所知,yield 应该首先在 UI 事件循环上执行所有操作,然后在当前上下文中恢复。真是大开眼界。 - nawfal
1个回答

4

async并不会神奇地让你的代码变得更好。 yield不是DoEvents(邪恶的)的直接替代品。需要一些工作来改变代码的对齐方式。

只要你不需要UI上下文,就可以将工作推到线程池中:

public async void button1_Click(object sender, EventArgs e) {
  await Task.Run(() => {
    var list = new List<int>();
    for (int i = 0; i < 10000; i++) {
      list.Add(i);
    }
    for (int i = 0; i < list.Count; i++) {
      Process(list[i]);
    }
  });
}

或者,您可以考虑使用IProgress<T>来分解UI特定的部分,或者使用Task.Run来处理非UI工作:

public async void button1_Click(object sender, EventArgs e) {
  var list = new List<int>();
  await Task.Run(() => {
    for (int i = 0; i < 10000; i++) {
      list.Add(i);
    }
  });

  for (int i = 0; i < list.Count; i++) {
    await ProcessAsync(list[i]);
  }
}

public static async Task ProcessAsync(int i) {
  await Task.Run(() => { ... }); // background
  myUi.Text = i.ToString() + " working"; // ui
  await Task.Run(() => { ... }); // more background
  myUi.Text = i.ToString() + " complete"; // ui
}

1
是的。我已经非常注重异步,使用了自己的异步库(.net 4.5之前)。但是当我阅读有关async/await的示例时,发现只有单击事件循环,并且Task.Yield似乎会让UI线程暂停。但是测试后发现实际上并不起作用。因此,网络上的示例要么是错误的,要么是我的代码有问题。Delay可以工作,Yield不能。 - Wolf5
Yield将会让出UI事件循环。但是它首先安排自己的继续。所以,执行继续是否优先于其他UI事件取决于事件循环。例如,GetMessage 为从UI循环中检索到的消息定义了一个优先级,这个优先级可能与Yield不兼容。 - Stephen Cleary
因此,人们不能信任Yield以正确地替换DoEvents。 延迟仍然有效。 但是,使用延迟比使用DoEvents更好吗? - Wolf5
这仍然是一种hack。你可以保留DoEvents;除了重构之外,没有好的替代方法。 - Stephen Cleary
2
就 .Net 4.5 而言,现在有一个 Dispatcher.Yield 静态方法,它的工作方式与 Task.Yield 不同。 - majocha

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