Reputation: 33
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
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
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
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
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
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
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