简体   繁体   English

wpf c#backgroundworker等到完成

[英]wpf c# backgroundworker wait until finished

I have several textboxes in my wpf application. 我的wpf应用程序中有几个文本框。 The LostFocus-Event of each textbox starts a backgroundworker to send the data to a connected serial port. 每个文本框的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
}

At this point, everything is working fine. 在这一点上,一切都很好。

But sometimes I have to send two data-points directly after each other and there I have a problem. 但有时我必须直接发送两个数据点,我有一个问题。

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);
}

The Backgroundworker is still running and I get an exception thrown. Backgroundworker仍在运行,我抛出一个异常。 Is it possible to wait after the first online_mode_send_worker.RunWorkerAsync(data) until it has finished and then start the second online_mode_send_worker.RunWorkerAsync(data) ? 是否可以在第一个online_mode_send_worker.RunWorkerAsync(data)之后等待,直到它完成,然后启动第二个online_mode_send_worker.RunWorkerAsync(data)

while(online_mode_send_worker.isBusy); is not working because the main-thread is blocking and the RunWorkerCompleted() is not thrown and so the Backgroundwoker is always busy. 因为主线程阻塞而且没有抛出RunWorkerCompleted() ,所以Backgroundwoker函数总是很忙。

I have found something like this, but Application.DoEvents() is not available in wpf. 我发现了类似的东西,但是在wpf中没有Application.DoEvents()

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

Here is a rough idea of what I mentioned in the comments. 以下是我在评论中提到的内容的粗略概念。

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);
        }
    }

}

You have a queue to hold on to messages that were sent while the background worker was busy and have the worker check the queue for any pending messages when it has completed doing its work. 您有一个队列来保留在后台工作程序繁忙时发送的消息,并让工作人员在完成其工作后检查队列中是否有任何待处理消息。

The messenger can be used like this. 信使可以像这样使用。

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();
}

It seems that I missed the serial stuff. 好像我错过了连续剧。 So what you want to do is synchronize your asynchronuouscalls: 所以你想要做的是同步你的异步调用:

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);
        }
    }
}

also see: 另见:

ORIGINAL ANSWER 原始答案

You can use Task instead of Backgroundworker. 您可以使用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();
}

I also would like to suggest that you make real objects instead of passing string arrays as arguments. 我还建议您创建实际对象,而不是将字符串数组作为参数传递。

For Example send BlinkLedRequest: 例如发送BlinkLedRequest:

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

and a corresponding method: 和相应的方法:

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

I think your should use RunWorkerCompleted event and add a delegate: 我认为您应该使用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);

Make sure you put there a condition to avoid infinite loop. 确保你放置一个条件,以避免无限循环。

Update: 更新:

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

Is not good aproach, because UI will be blocked on time of waiting. 不好的方法,因为UI会在等待时被阻止。 Better: 更好:

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

Original answer: 原始答案:

I advice not to use construction: while (bw.Busy) { ... } (it consumes cpu time), use synchronization objects, for example, ManualResetEvent 我建议不要使用构造: while (bw.Busy) { ... } (它消耗cpu时间),使用同步对象,例如,ManualResetEvent

BackgroundWorker is great class, but does not support waiting. BackgroundWorker很棒,但不支持等待。 Just create addition object for waiting: 只需创建添加对象等待:

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
}

I'd say that if you MUST wait until after the first "job" is done, that what you want is Task.ContinueWith() and change your interface accordingly. 我要说如果你必须等到第一个“工作”完成后,你想要的是Task.ContinueWith()并相应地改变你的界面。 The msdn page is good for it IMO, but watch out that you're waiting on the "correct" task object. msdn页面对IMO 有好处 ,但请注意您正在等待“正确”的任务对象。 Hint: it's the return value of ContinueWith() that you should call Wait() on. 提示:它应该是ContinueWith()的返回值,你应该调用Wait() This is a good pattern to do for launching a Task and then waiting for it later as long as you can keep the Task that is returned so you can wait on it. 这是一个很好的模式,用于启动一个Task ,然后等待它,只要你可以保留返回的Task ,这样你就可以等待它。

For a more generic "I only want one background thread doing things in the order they're added, and I want to wait until they're ALL done and I know when I'm done adding." 对于更通用的“我只希望一个后台线程以他们添加的顺序执行操作,我想等到他们完成所有操作并且我知道我什么时候完成添加。” I would suggest using a BlockingCollection<Action> with only one thread consuming them. 我建议使用BlockingCollection<Action> ,只有一个线程使用它们。 An example of how to do that is found in this other answer . 在另一个答案中可以找到如何做到这一点的一个例子。

If you need only call twice you can do this: 如果您只需要拨打两次电话,您可以这样做:

 bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        online_mode_send_worker.RunWorkerAsync(data2);
    }

But if you need to queue commands you need rewrite in another way Using Task. 但是如果你需要排队命令,你需要以另一种方式重写使用任务。 One Task where inside it you will have a for-loop where you will send your data through serial port sequentially. 一个任务,在其中你将有一个for循环,你将顺序通过串口发送数据。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM