C# 如何从单独的任务更新winforms UI?

C# 如何从单独的任务更新winforms UI?,c#,winforms,.net-3.5,task-parallel-library,C#,Winforms,.net 3.5,Task Parallel Library,我有一个现有的系统,有几个长期运行的操作。我希望找到一种简单的方法,将这些操作包装到任务中,并向用户发出忙碌状态通知 这是使用.NET3.5,我使用的是TPL后端端口。不幸的是,这里不能使用async/await 我试图以下面所示的简单测试形式提出解决方案,但遇到了一些困扰我的问题 下面的方法只显示前2-3个任务,之后直到所有任务完成,UI才会更新。为什么会这样 private void RunTaskUpdateBefore (string msg) { UpdateUI (msg); Tas

我有一个现有的系统,有几个长期运行的操作。我希望找到一种简单的方法,将这些操作包装到任务中,并向用户发出忙碌状态通知

这是使用.NET3.5,我使用的是TPL后端端口。不幸的是,这里不能使用async/await

我试图以下面所示的简单测试形式提出解决方案,但遇到了一些困扰我的问题

下面的方法只显示前2-3个任务,之后直到所有任务完成,UI才会更新。为什么会这样

private void RunTaskUpdateBefore (string msg)
{
UpdateUI (msg);
Task task = Task.Factory.StartNew (() =>
{
    // simulate some work being done
    Thread.Sleep (1000);
});
task.Wait ();  
}
我希望这个方法能起作用,但在所有任务完成之前不会更新UI

private void ChildTaskExternalWait (string msg)
{
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext ();

Task task = Task.Factory.StartNew (() =>
{
    Task.Factory.StartNew (() =>
    {
        UpdateUI (msg);
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);

    // simulate some work being done
    Thread.Sleep (1000);
});
task.Wait (); // I assume this is the problem
}
下面的方法非常有效,但我不确定是否可以在我的项目中使用它。这是一个相当大的复杂系统,操作发生在许多不同的类中,并且由我们无法修改的遗留系统控制。我假设我必须尝试创建某种全局任务队列来完成这项工作

private Task RunTaskInternalWaitOnPrevious (string msg, Task t1 = null)
{
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext ();
Task task = Task.Factory.StartNew (() =>
{
    if (t1 != null)
        {
        t1.Wait ();
        }
    Task.Factory.StartNew (() =>
    {
        UpdateUI (msg);
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);

    // simulate some work being done
    Thread.Sleep (2000);
});

return task;
}
我的测试表单实现:

private void button1_Click (object sender, EventArgs e)
{
// Only displays the first 2-3 tasks. After that UI doesn't get updated until all are complete
RunTaskUpdateBefore ("Task 1");
RunTaskUpdateBefore ("Task 2");
RunTaskUpdateBefore ("Task 3");
RunTaskUpdateBefore ("Task 4");
RunTaskUpdateBefore ("Task 5");
RunTaskUpdateBefore ("Task 6");
RunTaskUpdateBefore ("Task 7");

// this works with any number of tasks
// Unfortunately I'm not sure if I can use it in my application (see above notes)
//var t1 = RunTaskInternalWaitOnPrevious ("Task 1");
//var t2 = RunTaskInternalWaitOnPrevious ("Task 2", t1);
// and so on 
}
private void UpdateUI (string msg)
    {
    this.textBox1.Text += msg;
    this.textBox1.Text += System.Environment.NewLine;
    }
更新/澄清

*我的任务不能并行运行。
*我想通知用户,每个步骤都在进行中。
*在任务运行时,不能进行其他UI输入


这些任务并不是集中在一段特定的代码中,而是到处发生。对不起,我不知道该如何描述我的处境

在这个代码示例中,
button1\u Click
同步运行,并将阻止UI线程,直到它完成,因此,即使在它调用的方法中创建的任务想要更新UI线程,我怀疑这些尝试仍在排队,直到所有事情完成。在您看来,wait和async就是为了解决这个问题而设计的。在此之前,可以使用BackgroundWorkers进行同样的工作。我会将BackgroundWorker添加到您的表单中,将所有代码从
按钮1\u Click
移动到BackgroundWorker的
dowwork
方法,并在单击按钮1时启动后台工作程序。我相信BackgroundWorker是不受欢迎的,但可以与.NET3.5配合使用

如果您不熟悉BackgroundWorker类,这里有一个关于它的快速教程:


插入
Application.DoEvents
以暂停按钮1的执行,直到UI可以自我更新,这是另一种选择,但这种方法存在许多潜在问题。请参阅此线程以获取您的一长串公平警告:

当运行多个提供您想要显示的进度信息的线程时,您可以按照以下步骤进行操作:

  • 定义一个包含任务id和进度的UpdateInfo类 信息
  • 创建变量UpdateInfo=new List()
  • 如果要从任务发送进度信息,请创建并填写 一个UpdateInfo,然后将其推到列表上,即锁定(UpdateInfo){ updateInfo.Add(…);}
  • 在主窗体中创建一个计时器(例如,每250ms),在计时器事件中,弹出所有更新信息并相应刷新HMI

您可以在任务完成后立即使用
continueWith
更新线程

var task1 = new Task(() => Thread.Sleep(1000)); //replace with actual task

var task2 = task1.ContinueWith(t => UpdateUI(msg), CancellationToken.None, TaskContinuationOptions.None, uiScheduler);

task1.Start();

我认为在这一点上,我需要从一个简短的例子开始。TPL不是.NET3.5的选项