【问题标题】:How do I update my winforms UI from separate tasks?如何从单独的任务更新我的 winforms UI?
【发布时间】:2017-06-23 22:12:20
【问题描述】:

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

这是使用 .net 3.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 输入。

任务并没有集中到特定的代码段,而是在各处发生。我不确定如何描述我的情况,对不起。

【问题讨论】:

  • 我认为此时我需要从一个更短的示例重新开始。

标签: c# winforms .net-3.5 task-parallel-library


【解决方案1】:

您可以在任务完成后立即使用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 不是 .Net 3.5 的选项
【解决方案2】:

当运行多个提供您想要显示的进度信息的线程时,您可以如下进行:

  • 定义一个包含任务ID和进度的UpdateInfo类 信息,
  • 创建一个变量 UpdateInfos = new List(),
  • 当您想发送任务的进度信息时,创建并填写 一个 UpdateInfo,然后将其推送到列表中,即 Lock(UpdateInfos) { UpdateInfos.Add(...) ;}
  • 在主窗体中创建一个计时器(例如每 250 毫秒),并在计时器事件中弹出所有 UpdateInfo 并相应地刷新 HMI。

【讨论】:

    【解决方案3】:

    在此代码示例中,button1_Click 同步运行并将阻塞 UI 线程直到它完成,因此即使在它调用的方法中创建的任务想要更新 UI 线程,我怀疑这些尝试正在排队直到一切完成之后。就您而言, await 和 async 旨在解决此问题。在此可用之前,BackgroundWorkers 可以用来做同样的事情。我将在您的表单中添加一个 BackgroundWorker,将所有代码从 button1_Click 移动到 BackgroundWorker 的 DoWork 方法,并在单击 Button1 时启动后台工作程序。我相信 BackgroundWorkers 已被弃用,但可以在 .Net 3.5 上正常工作

    如果您不熟悉 BackgroundWorker 类,这里有一个快速教程:https://www.dotnetperls.com/backgroundworker

    插入Application.DoEvents 以暂停button1_Click 的执行直到UI 可以自行更新是另一种选择,但这种方法存在许多潜在问题。请参阅此主题以获取一长串公平警告:Use of Application.DoEvents()

    【讨论】:

      猜你喜欢
      • 2012-03-29
      • 2016-07-04
      • 1970-01-01
      • 2013-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多