So i'm reading a constant serial data stream from my arduino to validate some things in my program. However displaying these lock up the UI thread. So my "solution" is to make a buffer that keeps the serial data and then use a timer to put the data on the UI thread with intervals instead of a constant stream.
My code:
public partial class ConsoleWindow : Window
{
private SerialPort _serialPort;
private List<string> bufferStrings = new List<string>();
private readonly DispatcherTimer timer = new DispatcherTimer();
public ConsoleWindow(ref SerialPort serialPort)
{
InitializeComponent();
if (serialPort != null)
{
timer.Interval = new TimeSpan(0,0,0,0,80);
timer.Tick += PopQueue;
_serialPort = serialPort;
_serialPort.DataReceived += DataReceived;
timer.Start();
}
}
private void PopQueue(object sender, EventArgs e)
{
var queue = bufferStrings;
foreach (var queueString in queue)
{
AppendText(queueString);
}
bufferStrings.Clear();
}
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (_serialPort != null)
{
bufferStrings.Add(((SerialPort)sender).ReadLine());
//AppendText(((SerialPort) sender).ReadLine());
}
}
public void AppendText(string text)
{
Application.Current.Dispatcher.Invoke(() =>
{
if (Output.Inlines.Count > 100)
{
Output.Inlines.Remove(Output.Inlines.FirstInline);
}
Output.Inlines.Add(text);
ScrollViewer.ScrollToBottom();
});
}
}
The issue with this is that I get an exception: System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'. I know why it happens, but i have no idea how i could do this properly. And no idea of what to google either.
Here are two solutions you can take to prevent the InvalidOperationException:
Copy the buffer to a new list before iterating through its contents. You can do so by calling var queue = bufferStrings.ToList(); Note that you must include using System.Linq; to use ToList().
Make the iteration thread safe by surrounding it with the lock keyword:
private void PopQueue(object sender, EventArgs e)
{
lock(bufferStrings)
{
foreach (var queueString in bufferStrings)
{
AppendText(queueString);
}
bufferStrings.Clear();
}
}
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (_serialPort != null)
{
lock(bufferStrings)
{
bufferStrings.Add(((SerialPort)sender).ReadLine());
//AppendText(((SerialPort) sender).ReadLine());
}
}
}
The simplest solution is to synchronize the accesses to the bufferStrings queue using a Monitor through the lock construct:
private void PopQueue(object sender, EventArgs e)
{
lock (bufferStrings)
{
foreach (var queueString in bufferStrings)
{
AppendText(queueString);
}
bufferStrings.Clear();
}
}
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (_serialPort != null)
{
lock (bufferStrings)
{
bufferStrings.Add(((SerialPort)sender).ReadLine());
//AppendText(((SerialPort) sender).ReadLine());
}
}
}
The problem is that while you're iterating over an IEnumerable using foreach, the collection is being changed in another thread.
What you need is a collection that you can concurrently add to and read from.
At the top of the file add
using System.Collections.Concurrent;
Change this:
private List<string> bufferStrings = new List<string>();
to
private ConcurrentQueue<string> bufferStrings = new ConcurrentQueue<string>();
Change
bufferStrings.Add(((SerialPort)sender).ReadLine());
to
bufferStrings.Enqueue(((SerialPort)sender).ReadLine());
Then, you can read from the queue without worrying about whether something else is writing to it:
private void PopQueue(object sender, EventArgs e)
{
while (bufferStrings.TryDequeue(out string dequeued))
AppendText(dequeued);
}
This just keeps trying to take items out the queue until there are no more. TryDequeue returns false when the queue is empty. If you keep adding items while this method is running it will just keep processing them.
ConcurrentQueue
Reactive Extensions provide most of this functionality out of the box.
Check out Intro to RX, Observable Timer, Replacing Events, ReactiveUI
Related
Can this code provocate a deadlock? I am afraid that the handler is executed suddenly in the middle of the execution of the DoWork function (which is also locked).
private object lockObject = new object();
public void DoWork()
{
lock(lockObject)
{
ChannelFactory<TChannel> channelFactory = CreateChannelFactory();
channelFactory.Faulted += (sender, e) => OnChannelFactoryFaulted(sender, e);
// Some code.. access to dictionary...
}
}
private void OnChannelFactoryFaulted(object sender, EventArgs e)
{
lock (lockObject)
{
// Some code.. access to dictionary..
}
}
Thanks
My question is how do you update your ui, with data from serialport, while still updating other ui components? I have tried using a background worker, but it seems to block the ui, while the data is streaming in.
Image of form:
My code is:
public partial class Form1 : Form
{
static bool _continue;
static SerialPort _serialPort;
BackgroundWorker dataWorker;
string message;
public delegate void UpdateListboxData();
private void buttonPortConnect_Click(object sender, EventArgs e)
{
// Set the read/write timeouts
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;
_serialPort.Open();
_continue = true;
dataWorker = new BackgroundWorker();
dataWorker.RunWorkerAsync();
dataWorker.DoWork += StartDataWork;
}
private void StartDataWork(object sender, DoWorkEventArgs e)
{
Delegate del = new UpdateListboxData(DataRead);
this.Invoke(del);
}
private void DataRead()
{
while (_continue)
{
try
{
message = _serialPort.ReadLine();
}
catch (TimeoutException) { }
}
}
}
you have to use Serial Port DataReceived Event and Delegate
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (TextBox.InvokeRequired)
TextBox.Invoke(new myDelegate(updateTextBox));
}
public delegate void myDelegate();
public void updateTextBox()
{
int iBytesToRead = serialPort1.BytesToRead;
//Your Codes For reding From Serial Port Such as this:
char[] ch = new char[?];
serialPort1.Read(ch, 0, ?? ).ToString();
//........
}
The way to update a winforms UI is to use if (control.InvokeRequired) {...} and Invoke as shown in the example here How do I update the GUI from another thread?
You should not read data inside the invoke'd function. This is blocking your UI.
Either read data directly in "DoWork" and invoke the delegate only with data.
Or you might use the DataReceived event of SerialPort.
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);
}
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:
https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
https://msdn.microsoft.com/en-us/library/de0542zz(v=vs.110).aspx
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)
{
....
}
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.
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.
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
}
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
I have an application that takes a Wireshark capture file and feeds it (all the containing packets) into a network adapter.
Currently my application is a big mess - countless global variables & every task opened within a seperate BackgroundWorker instance etc...
To clarify - the purpose of using BackgroundWorkers here (more specifically the DoWork, RunWorkerCompleted events and the WorkerReportsProgress property) is to prevent the packet feeding operations from freezing my UI. To stop an operation, I need access to these workes - for now, global variables are used to achieve this.
So the question is - should I place my BackgroundWorker objects inside a Singleton-type class and then call this object when necessary?
From a technical point of view is possible, after all the singleton pattern is a design pattern that restricts the instantiation of a class to one object
you can try something like this
public class BackWorkerSingleton
{
private BackgroundWorker _backgroundWorker;
private static readonly object myLock = new object();
private static BackWorkerSingleton _backWorkerSingleton = new BackWorkerSingleton();
public delegate void ReportProgressEventHandler(object sender,MyEventsArgs e);
public event ReportProgressEventHandler ReportProgress = delegate{ };
private BackWorkerSingleton()
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(_backgroundWorker_ProgressChanged);
}
void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.ReportProgress( this, new MyEventsArgs(){Progress = e.ProgressPercentage});
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// do your work here
}
public void StartTheJob()
{
_backgroundWorker.RunWorkerAsync();
}
public static BackWorkerSingleton Worker
{
get
{
lock (myLock)
{
if (_backWorkerSingleton == null)
{
_backWorkerSingleton = new BackWorkerSingleton();
}
}
return _backWorkerSingleton;
}
}
}
class MyEventsArgs:EventArgs
{
public int Progress { get; set; }
}
and here the report progress
private void Form1_Load(object sender, EventArgs e)
{
BackWorkerSingleton.Worker.ReportProgress += new BackWorkerSingleton.ReportProgressEventHandler(Worker_ReportProgress);
}
void Worker_ReportProgress(object sender, MyEventsArgs e)
{
}
and call it like this
BackWorkerSingleton.Worker.StartJob()
I have a listbox with filenames. When the selected index is changed I load the file.
I want something like jQuery's HoverIntent that delays the action of loading the file for a short time so the user can use the down arrow and quickly cycle through the items in the list without the application trying to load each one. Thread.Sleep pauses the whole app so a user can't select another list item until the sleep completes, this is obviously not what I want.
This will work if your using WinForms, make a call to the InitTimer method in the Form constructor.
Load the file in the _timer_Tick event handler. To change the delay set the Interval property in InitTimer to another value.
private System.Windows.Forms.Timer _timer;
private void InitTimer()
{
_timer = new Timer { Interval = 500 };
_timer.Tick += _timer_Tick;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
// TODO: Load file here
}
Use Threading to separate the loading from your GUI.
This should get you started:
public partial class MainWindow : Window
{
CancellationTokenSource cts;
bool loading;
private void SelectedIndexChanged(int index)
{
if (loading)
cts.Cancel();
cts = new CancellationTokenSource();
var loader = new Task.Delay(1000);
loader.ContinueWith(() => LoadFile(index))
.ContinueWith((x) => DisplayResult(x));
loader.Start();
}
private void DisplayResult(Task t)
{
// TODO: Invoke this Method to MainThread
if (!cts.IsCancellationRequested)
{
// Actually display this file
}
}
Could not test, as I'm still on .net 4 whereas Task.Delay() is .net 4.5
You may need to add another field in the form for the file content transfer from the tasks to the GUI.
Winforms:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource _cancel;
private object _loadLock = new object();
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
lock (_loadLock)
{
handleCancellation();
var loader = new Task((chosenFileItemInListbox) =>
{
Thread.Sleep(1000);
LoadFile(chosenFileItemInListbox);
}, listBox1.SelectedItem, _cancel.Token);
}
}
private bool handleCancellation()
{
bool cancelled = false;
lock (_loadLock)
{
if (_cancel != null)
{
if (!_cancel.IsCancellationRequested)
{
_cancel.Cancel();
cancelled = true;
}
_cancel = null;
}
}
return cancelled;
}
private void LoadFile(object chosenFileItemInListbox)
{
if (handleCancellation())
{
return;
}
}
}
The code above could also be applied to WPF, but WPF contains some built in magic for handling delays and cancellation of previous updates.
<ListBox SelectedItem="{Binding Path=SelectedFile, Delay=1000}" />