ecom_jo
ecom_jo

Reputation: 33

wpf c# backgroundworker wait until finished

I have several textboxes in my wpf application. The LostFocus-Event of each textbox starts a backgroundworker to send the data to a connected serial port.

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. 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)?

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.

I have found something like this, but Application.DoEvents() is not available in wpf.

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

Upvotes: 3

Views: 1375

Answers (6)

Everton Santos
Everton Santos

Reputation: 162

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.

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

Upvotes: 0

Alexey Obukhov
Alexey Obukhov

Reputation: 854

Update:

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

Is not good aproach, because UI will be blocked on time of waiting. 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

BackgroundWorker is great class, but does not support waiting. 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
}

Upvotes: 0

Kevin Anderson
Kevin Anderson

Reputation: 7010

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. The msdn page is good for it IMO, but watch out that you're waiting on the "correct" task object. Hint: it's the return value of ContinueWith() that you should call Wait() on. 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.

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. An example of how to do that is found in this other answer.

Upvotes: 0

Dragos Stoica
Dragos Stoica

Reputation: 1925

I think your should use RunWorkerCompleted event and add a delegate:

      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.

Upvotes: 0

Nkosi
Nkosi

Reputation: 246998

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

Upvotes: 2

Mat
Mat

Reputation: 2072

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.

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:

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

and a corresponding method:

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

Upvotes: 0

Related Questions