Task.WaitAll冻结应用程序C#

时间:2014-01-16 16:45:57

标签: c# task-parallel-library task

我想尝试Threading.Task(C#)并行运行一些工作。在这个简单的例子中,我有一个带进度条和按钮的表单。单击时,将调用RunParallel函数。没有Task.WaitAll()它似乎运行良好。但是,使用WaitAll语句,表单显示并且没有任何反应。我不明白我在下面的设置中做错了什么。 提前谢谢。

public partial class MainWindow : Form
{
    public delegate void BarDelegate();
    public MainWindow()
    {
    InitializeComponent();
    }
    private void button_Click(object sender, EventArgs e)
    {
        RunParallel();
    }
    private void RunParallel() {
        int numOfTasks = 8;
        progressBar1.Maximum = numOfTasks;
        progressBar1.Minimum = 0;
        try
        { 
            List<Task> allTasks = new List<Task>();
            for (int i = 0; i < numOfTasks; i++)
            {
                allTasks.Add(Task.Factory.StartNew(() => { doWork(i); }));
            }
            Task.WaitAll(allTasks.ToArray());
        }
        catch { }  
    }
    private void doWork(object o1)
    {
        // do work...
        // then
        this.Invoke(new BarDelegate( UpdateBar )); 
    }
    private void UpdateBar()
    {
        if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
    }
}

4 个答案:

答案 0 :(得分:12)

您正在等待UI线程。这会冻结用户界面。不要那样做。

此外,您处于死锁状态,因为Invoke等待UI线程取消阻止。

我的建议:尽可能使用async/await

不要吞下例外。你正是以这种方式为自己创造未来的工作。

答案 1 :(得分:4)

这就是WaitAll所做的事情,它会阻止所有任务完成。任务无法完成,因为Invoke将在UI线程上运行其操作,该线程已被WaitAll

阻止

要使代码真正异步运行,请尝试以下方法:

private void RunParallel() {
    int numOfTasks = 8;
    progressBar1.Maximum = numOfTasks;
    progressBar1.Minimum = 0;
    try
    { 
        var context=TaskScheduler.FromCurrentSynchronizationContext()
        for (int i = 0; i < numOfTasks; i++)
        {
            Task.Factory.StartNew(()=>DoWork(i))
                    .ContinueWith(()=>UpdateBar(),context);
        }

    }
    catch (Exception exc)
    { 
      MessageBox.Show(exc.ToString(),"AAAAAARGH");
    }  
}
private void DoWork(object o1)
{
    // do work...

}
private void UpdateBar()
{
    if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
}

在这种情况下,每次任务完成时都会在UI上下文中调用UpdateBar,而不会导致任何阻塞。

请注意,这不是生产代码,只是一种显示如何异步运行方法并更新UI而不会阻塞的方法。您需要阅读有关任务的内容,以了解它们的作用以及它们的工作原理。

在.NET 4.5+中使用async/await,您可以用更简单的方式编写它。以下将在后台执行DoWork而不会阻塞,但每次任务完成时仍会更新UI。

private async void button1_Click(object sender, EventArgs e)
{
        int numOfTasks = 8;
        progressBar1.Maximum = numOfTasks;
        progressBar1.Minimum = 0;
        try
        {
            for (int i = 0; i < numOfTasks; i++)
            {
                await Task.Run(() => DoWork(i));
                UpdateBar();
            }

        }
        catch (Exception exc)
        {
            MessageBox.Show(exc.ToString(), "AAAAAARGH");
        }
    }

await告诉编译器在右边的任务完成后,在原始(UI)线程上生成代码以执行它下面的任何内容:

答案 2 :(得分:1)

在doWork中,您调用this.Invoke(...)等待UI线程处理消息。不幸的是,你的UI线程没有处理消息,因为它正在等待所有doWork(...)完成。

最简单的解决方法是在doWork中将this.Invoke更改为this.BeginInvoke(它将发送消息但不等待它们被处理)。 虽然,我必须管理它仍然不是书本,因为UI不应该等待任何事情。 简单模式(预异步/等待时代):

Task.Factory.StartNew(() => {
    ... work ...
})
.ContinueWith((t) => {
    ... updating UI (if needed) ...
}, TaskScheduler.FromCurrentSynchronizationContext());

答案 3 :(得分:0)

RunParallel阻止,直到完成所有任务。使用不同的机制来通知UI。