【问题标题】:Wait for BackgroundWorker to complete?等待 BackgroundWorker 完成?
【发布时间】:2012-05-18 09:34:36
【问题描述】:

我有 2 个异步进程,需要一个接一个地调用(一个是 XML 创建 backgroundworker,另一个是使用在第一个进程中创建的 XML 文件的稀有 BW )。这些线程的主要原因是停止 UI 冻结并通过进度条的方式提供状态更新 - 因为这种同步方法是不可取的/不可能的。

if (!CreateXMLBW.IsBusy)
{
CreateXMLBW.RunWorkerAsync("");
}
if (!CreateRarBW.IsBusy)
{
CreateRarBW.RunWorkerAsync();
}

我不能将第二个 BW 放在第一个的完成事件中,因为进程可以单独使用,因此如果我只想创建 XML 文件,我可以这样做。

我尝试过使用AutoResetEventWaitOne,但这(无论出于何种原因)仍然不起作用。

还有其他方法可以等待 BW 完成而不冻结主 UI 线程吗?

【问题讨论】:

  • 为什么不创建一个新的 Worker 来同步运行这 2 个 Worker 呢?然后你异步运行这个新的工作人员
  • 我喜欢这个想法,但不符合 DRY。
  • 很干,你不要重复代码,因为你重用了另外 2 个工人。无论如何,下面提出的任务解决方案要优雅得多:)
  • 如果您想向进度条/更新标签等报告进度,这是不可能的,因为这会变成一个跨线程调用。但是是的,Task 是一个不错的解决方案,我需要一段时间才能摆脱 BGW 的思维方式。

标签: c# .net multithreading visual-studio-2010 backgroundworker


【解决方案1】:

您的场景正是 Task 的设计目的。在您的特定情况下,您的代码可能如下所示:

public delegate void UpdateUI(int progress);

private void RunOneAfterAnotherAsync()
{
    Task<XmlElement> task = Task.Factory.StartNew<XmlElement>(CreateXMLBW);
    task.ContinueWith(CreateRarBW);
}

private XmlElement CreateXMLBW()
{
    // your code

    // progress
    progressBar1.Invoke((UpdateUI)UpdateProgressBar, new object[] {progressValue});

    // result
    XmlDocument doc = new XmlDocument();
    return doc.CreateElement("element");
}

private void CreateRarBW(Task<XmlElement> task)
{
    CreateRarBW(task.Result);
}

private void CreateRarBW(XmlElement arg)
{
    // your code
}

public void UpdateProgressBar(int value)
{
    this.progressBar1.Value = value;
}

RunOneAfterAnotherAsync 没有阻塞,您的 2 个方法一个接一个地异步运行。 CreateRarBW 仅在 CreateXMLBW 无异常结束时运行,但您可以通过在 ContinueWith 中使用附加参数来更改它。

您可以做的远不止此示例所示 - 我鼓励您探索 Task 类。

编辑

我稍微扩展了示例,将第一个任务传递的结果合并到第二个任务中。还添加了 UI 进度示例。

【讨论】:

  • 我可以在没有跨线程异常的情况下从任务中报告进度或类似于进度条吗?
  • 要报告进度,您需要从任务中调用某个代表。如果你想直接从任务更新 UI,你必须使用Control.Invoke
【解决方案2】:

您也可以使用Task,如本例所示:

class Program
{
    static XElement CreateXml()
    {
        System.Threading.Thread.Sleep(1000);
        return XElement.Parse(@"<FooBar>Hi!</FooBar>");
    }

    static void ProceedXml(XElement xml)
    {
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine(xml.ToString());
    }

    public static void Main()
    {
        Task.Factory.StartNew<XElement>(CreateXml)
                    .ContinueWith(t => ProceedXml(t.Result));
        Console.ReadKey();
    }
}

【讨论】:

    【解决方案3】:

    如果你不想阻塞 UI 直到等待事件(所以我假设你会做点什么) 您可以在DoWork() 的末尾引发一个事件,UI 线程可以接收它。

    如果你使用 4.0 并且可以避免使用 BackgroundWorker,你可以使用来自 TPL 的Task.ContinueWith

    伪代码可能如下所示:

       Action action =(() => DoWorkMethod());
       Task.Factory.StartNew(() => action()).ContinueWith(()=>CallAfterComplete());
    

    【讨论】:

      【解决方案4】:

      您的 UI 可以处理第一个 BW 上的 RunWorkerCompleted 事件并调用第二个 BW。

      【讨论】:

      • 根据我的问题 - “我不能将第二个 BW 放在第一个的完成事件中,因为这些进程可以单独使用,因此如果我只想创建 XML 文件,我可以这样做。”这意味着如果我把它放在 RunWorkerCompleted 事件中,它总是会触发,这是我不想要的。
      • @Myles 好吧,好吧,但我假设您的 UI 会知道它是否要运行第二个 BW。我不是说你会总是运行它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多