【问题标题】:wpf c# backgroundworker wait until finishedwpf c# backgroundworker 等到完成
【发布时间】:2017-01-10 13:54:52
【问题描述】:

我的 wpf 应用程序中有几个文本框。每个文本框的 LostFocus-Event 启动一个后台工作程序,将数据发送到连接的串口。

private readonly BackgroundWorker online_mode_send_worker = new BackgroundWorker();
online_mode_send_worker.DoWork += online_mode_send_worker_DoWork;
online_mode_send_worker.RunWorkerCompleted += online_mode_send_worker_RunWorkerCompleted;

private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
    online_mode_send_worker.RunWorkerAsync(data);
}

private void online_mode_send_worker_DoWork(object sender, DoWorkEventArgs e)
{
    List<object> data = (List<object>)e.Argument;
    Port.WriteLine(STARTCHARACTER + XMLSET + XML_TAG_START + data[0] + XML_TAG_STOP + data[1] + ENDCHARACTER);
    string received = Port.ReadLine();
}

private void online_mode_send_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //do some things after worker completed
}

此时,一切正常。

但有时我必须一个接一个地直接发送两个数据点,我遇到了问题。

private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
    online_mode_send_worker.RunWorkerAsync(data1);
    //wait until backgroundworker has finished 
    online_mode_send_worker.RunWorkerAsync(data2);
}

Backgroundworker 仍在运行,我得到一个异常抛出。 是否可以在第一个online_mode_send_worker.RunWorkerAsync(data) 完成后等待,然后再启动第二个online_mode_send_worker.RunWorkerAsync(data)

while(online_mode_send_worker.isBusy); 不工作,因为主线程阻塞,RunWorkerCompleted() 没有被抛出,所以Backgroundwoker 总是很忙。

我找到了类似的东西,但是 Application.DoEvents() 在 wpf 中不可用。

while (online_mode_send_worker.IsBusy)
{
    Application.DoEvents();
    System.Threading.Thread.Sleep(100);
}

【问题讨论】:

  • 除非某些东西已经存在,否则我认为您可以创建一个封装后台工作者的服务。这个类也将有一个队列。保存任何数据。当工作开始时,工人处理数据。如果要发送更多数据,请将其添加到队列中。当工作人员完成时,它会检查队列以查看是否还有更多工作要做,并将下一个工作出列并再次调用后台工作人员,否则正常完成。
  • 使用“任务”而不是后台工作人员。使用 Backgroundworker 似乎真的过时了。 dotnetperls.com/async

标签: c# wpf backgroundworker


【解决方案1】:

这是我在 cmets 中提到的粗略概念。

public class Messenger {
    private readonly BackgroundWorker online_mode_send_worker = new BackgroundWorker();
    private readonly ConcurrentQueue<object> messages;

    public Messenger() {
        messages = new ConcurrentQueue<object>();
        online_mode_send_worker.DoWork += online_mode_send_worker_DoWork;
        online_mode_send_worker.RunWorkerCompleted += online_mode_send_worker_RunWorkerCompleted;
    }

    public void SendAsync(object message) {
        if (online_mode_send_worker.IsBusy) {
            messages.Enqueue(message);
        } else {
            online_mode_send_worker.RunWorkerAsync(message);
        }
    }

    public Action<object> MessageHandler = delegate { };

    private void online_mode_send_worker_DoWork(object sender, DoWorkEventArgs e) {
        if (MessageHandler != null)
            MessageHandler(e.Argument);
    }

    private void online_mode_send_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        object nextMessage = null;
        if (messages.Count > 0 && messages.TryDequeue(out nextMessage)) {
            online_mode_send_worker.RunWorkerAsync(nextMessage);
        }
    }

}

您有一个队列来保存在后台工作人员忙碌时发送的消息,并让工作人员在完成工作后检查队列中是否有任何待处理的消息。

信使可以这样使用。

private Messenger messenger = new Messenger();

private void Initialize() { //I would expect this to be in the constructor
    messenger.MessageHandler = MessageHandler;
}

private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
    messenger.SendAsync(data);
}

private void MessageHandler(object message)
{
    List<object> data = (List<object>)message;
    Port.WriteLine(STARTCHARACTER + XMLSET + XML_TAG_START + data[0] + XML_TAG_STOP + data[1] + ENDCHARACTER);
    string received = Port.ReadLine();
}

【讨论】:

  • 我很难找到一个具体的问题,我相信它会在这种情况下工作。我的主要抱怨仍然是它是一个非标准且略微过于复杂的解决方案。仍然是 +1。
【解决方案2】:

看来我错过了连载的东西。所以你想要做的是同步你的异步调用:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Task.Run(() => mySerialDevice1.WriteData(data1));
    Task.Run(() => mySerialDevice1.WriteData(data2));
}

public class SerialDevice
{
    public Port Port { get; set; }
    public object _LockWriteData = new object();
    public void WriteData(string data)
    {
        lock(_LockWriteData)
        {
            Port.WriteLine(data);
        }
    }
}

另见:

原始答案

您可以使用 Task 代替 Backgroundworker。

private void Button_Click(object sender, RoutedEventArgs e)
{   
    Task.Run(() => OnlineModeSendData(data1));
    Task.Run(() => OnlineModeSendData(data2));
}
private void OnlineModeSendData(List<string> data)
{
    Port.WriteLine(STARTCHARACTER + XMLSET + XML_TAG_START + data[0]+ XML_TAG_STOP + data[1] + ENDCHARACTER);
    string received = Port.ReadLine();
}

我还想建议您制作真实的对象,而不是将字符串数组作为参数传递。

例如发送 BlinkLedRequest:

public class BlinkLedRequest
{
     public int LedId{get;set;}
     public int DurationInMilliseconds {get;set}
}

以及对应的方法:

public void SendBlinkLed(BlickLedRequest request)
{
....
}

【讨论】:

  • 您似乎认为串行端口是线程安全的可重入端口。不是。
  • 另外,async void 作为 Task.Run() 的目标?不要那样做。
  • 或者认真编辑一下。 BlinkingLight 的东西可能有用但与问题无关。
【解决方案3】:

我认为你应该使用 RunWorkerCompleted 事件并添加一个委托:

      online_mode_send_worker.RunWorkerCompleted += (s, ev) =>
                {
                    if (ev.Error != null)
                    {
                        //log Exception
                    }
                    //if(conditionToBrake)
                    //    return;
                    online_mode_send_worker.RunWorkerAsync(data2);
                };
 online_mode_send_worker.RunWorkerCompleted(data1);

确保你设置了一个条件来避免无限循环。

【讨论】:

    【解决方案4】:

    我想说,如果您必须等到第一个“工作”完成后,您想要的是 Task.ContinueWith() 并相应地更改您的界面。 msdn page is good for it IMO,但请注意您正在等待“正确”的任务对象。提示:这是ContinueWith() 的返回值,您应该调用Wait()。这是启动Task 然后稍后等待它的好模式,只要您可以保留返回的Task 以便您可以等待它。

    对于更通用的“我只希望一个后台线程按照添加的顺序执行操作,并且我想等到它们全部完成并且我知道何时完成添加。”我建议使用BlockingCollection&lt;Action&gt;,只有一个线程在使用它们。在in this other answer 中找到了如何做到这一点的示例。

    【讨论】:

      【解决方案5】:

      更新:

      bw.RunWorkerAsync(data1);
      //wait here
      bw.RunWorkerAsync(data2);
      

      不是好方法,因为 UI 会在等待时被阻塞。更好:

      bw.RunWorkerAsync(new object[] { data1, data2 }); //or new object[] { data1 } if no data2
      

      原答案:

      我建议不要使用构造:while (bw.Busy) { ... }(它会消耗 cpu 时间),使用同步对象,例如 ManualResetEvent

      BackgroundWorker 是很棒的类,但不支持等待。只需创建等待的附加对象:

      var bw = new BackgroundWorker();
      bw.DoWork += Bw_DoWork;
      bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
      bool wasError;
      ManualResetEvent e = null;
      
      private void TextBox_LostFocus(object sender, RoutedEventArgs e)
      {
          if (e != null)
              return;
          wasError = false;
          e = new ManualResetEvent(false); //not signaled
          bw.RunWorkerAsync(data1);
          e.Wait(); //much better than while(bw.Busy())
          if (!wasError)
             bw.RunWorkerAsync(data2);
          e = null;
      }
      
      private void Bw_DoWork(object sender, DoWorkEventArgs e)
      {
          //background work in another thread
      }
      
      private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
          if (e.Error != null)
          {
              //catch exception here
              wasError = true;
          }
          e.Set(); //switch to signaled
      }
      

      【讨论】:

        【解决方案6】:

        如果您只需要调用两次,您可以这样做:

         bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
        
            void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                online_mode_send_worker.RunWorkerAsync(data2);
            }
        

        但是,如果您需要将命令排队,则需要使用 Task 以另一种方式重写。 一个任务,在其中你将有一个 for 循环,你将通过串行端口顺序发送数据。

        https://msdn.microsoft.com/pt-br/library/system.threading.tasks.task(v=vs.110).aspx

        【讨论】:

          猜你喜欢
          • 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
          相关资源
          最近更新 更多