From apps I've written and one I've inherited, I have a continuing desire to better understand the thread-safety issues of loading data on a background thread. Suppose I have a simple, single-window Windows Forms app with a "Load" button and a BackgroundWorker:
The button's Click handler calls loadBackgroundWorker.RunWorkerAsync(), and the worker's DoWork handler creates and initializes an object of type Document which, after loading, it stores in the form's LoadedDocument property. In the worker's RunWorkerCompleted handler, a MessageBox displays the properties of the LoadedDocument. I know this is all hard to visualize, so I'm including complete code. Sorry that it makes the question so long to read.
Here's the form's code:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace BackgroundLoadTest
{
public partial class Form1 : Form
{
private Document _loadedDocument;
public Document LoadedDocument
{
get
{
lock (this)
{
return _loadedDocument;
}
}
set
{
lock (this)
{
_loadedDocument = value;
}
}
}
public Form1()
{
InitializeComponent();
loadBackgroundWorker.DoWork += new DoWorkEventHandler(loadBackgroundWorker_DoWork);
loadBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(loadBackgroundWorker_RunWorkerCompleted);
}
void loadBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Document d = new Document();
d.Property1 = "Testing";
d.Property2 = 1;
d.Property3 = 2;
this.LoadedDocument = d;
}
void loadBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Document loaded with Property1 = " +
LoadedDocument.Property1 + ", Property2 = " +
LoadedDocument.Property2 + ", Property3 = " +
LoadedDocument.Property3);
}
private void loadButton_Click(object sender, EventArgs e)
{
loadBackgroundWorker.RunWorkerAsync();
}
}
}
Here's the code for the Document class:
using System;
namespace BackgroundLoadTest
{
public class Document
{
public string Property1 { get; set; }
public double Property2 { get; set; }
public int Property3 { get; set; }
}
}
My question is:
What thread-safety/memory-visibility problems do you see with this code, or what would you do differently given the goal of loading data on the background thread and eventually using the loaded data on the UI thread?
Is the locking in the LoadedDocument property sufficient to ensure that data initialized in the background thread will be visible to the UI thread? Is the locking necessary? I really want to understand the seemingly very common problem of loading complex documents on a background thread while keeping the GUI responsive, and I know it's tricky stuff.
Edit: to be clear, what I'm most concerned about here is memory visibility. I want to be sure that all the data initialization done by the background thread becomes visible to the GUI thread when the worker completes. I don't want changes getting stuck in a CPU cache and remaining invisible to threads on other CPUs. I don't know how to state my concerns better because they're still rather vague to me.
Locking around your getters and setters do nothing, assigning a reference type to a variable is an atomic operation.
This is plain wrong. Locking introduces memory barriers and thereby prevents instruction reordering and makes cached values visible to other threads. Accessing fields or properties (which also access fields) from different threads without synchronization isn't guaranteed to always work and can't be considered correct code.
What you're doing is accessing the LoadedDocument property from both your background thread and your UI thread. As you have implemented locking in there, this is correct code and will be thread safe.
The DoWorkEventArgs argument in your loadBackgroundWorker_DoWork method has a Result property which should be used to set the result of the background work. The RunWorkerCompletedEventArgs.Result property then can be used to access this value. Try the following:
void loadBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Document d = new Document();
d.Property1 = "Testing";
d.Property2 = 1;
d.Property3 = 2;
e.Result = d;
}
void loadBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.LoadedDocument = (Document)e.Result;
MessageBox.Show("Document loaded with Property1 = " +
LoadedDocument.Property1 + ", Property2 = " +
LoadedDocument.Property2 + ", Property3 = " +
LoadedDocument.Property3);
}
This tutorial is one of the most comprehensive and understandable resources in regard to multithreading in .NET which I would highly recommend. Your question would have been answered here.
Edit: Clarification of how BackgroundWorker synchronizes stuff
Still, I'm curious what magic goes on in BackgroundWorker that makes data passed via e.Result fully visible to the GUI thread.
Looking into reference source of Background worker, it is not really obvious how the result is synchronized between threads:
private void WorkerThreadStart(object argument)
{
object workerResult = null;
Exception error = null;
bool cancelled = false;
try
{
DoWorkEventArgs doWorkArgs = new DoWorkEventArgs(argument);
OnDoWork(doWorkArgs);
if (doWorkArgs.Cancel)
{
cancelled = true;
}
else
{
workerResult = doWorkArgs.Result;
}
}
catch (Exception exception)
{
error = exception;
}
RunWorkerCompletedEventArgs e =
new RunWorkerCompletedEventArgs(workerResult, error, cancelled);
asyncOperation.PostOperationCompleted(operationCompleted, e);
}
This happens on the background thread. The last line then marshals back to the UI thread. Looking further down the stack, there are no lock statements or other synchronization directives there. So how is this made thread safe?
Looking into the RunWorkerCompletedEventArgs, we find no synchronization code either. But there is some strange attribute over there:
[HostProtection(SharedState = true)]
public class RunWorkerCompletedEventArgs : AsyncCompletedEventArgs
MSDN explains:
When SharedState is true, it indicates that a state is exposed that
might be shared between threads.
So putting this attribute above your class obviously makes its members thread safe by synchronizing their access. Is this awesome? I think so. Should you use this in your code? Probably not.
Related
To simplify the explanation of the strange behavior I am experiencing, I have this simple class named Log which fires 1 log events every 1000msec.
public static class Log
{
public delegate void LogDel(string msg);
public static event LogDel logEvent;
public static void StartMessageGeneration ()
{
for (int i = 0; i < 1000; i++)
{
logEvent.Invoke(i.ToString());
Task.Delay(1000);
}
}
}
I have the Form class below which is subscribed to the log events of the Log class so it can handle them and display in a simple text box.
Once a log message arrives, it is added to a list. Every 500msec, a timer object access that list so its content can be displayed in a text box.
public partial class Form1 : Form
{
private SynchronizationContext context;
private System.Threading.Timer guiTimer = null;
private readonly object syncLock = new object();
private List<string> listOfMessages = new List<string>();
public Form1()
{
InitializeComponent();
context = SynchronizationContext.Current;
guiTimer = new System.Threading.Timer(TimerProcessor, this, 0, 500);
Log.logEvent += Log_logEvent;
}
private void Log_logEvent(string msg)
{
lock (syncLock)
listOfMessages.Add(msg);
}
private void TimerProcessor(object obj)
{
Form1 myForm = obj as Form1;
lock (myForm.syncLock)
{
if (myForm.listOfMessages.Count == 0)
return;
myForm.context.Send(new SendOrPostCallback(delegate
{
foreach (string item in myForm.listOfMessages)
myForm.textBox1.AppendText(item + "\n");
}), null);
listOfMessages.Clear();
}
}
private void button1_Click(object sender, EventArgs e)
{
Log.StartMessageGeneration();
}
}
The problem I see is that sometimes, there is a dead lock (application stuck). Seems that the 2 locks (1st one for adding to the list and the 2nd one for "retrieving" from the list) are somehow blocking each others.
Hints:
1) reducing the rate of sending the messages from 1 sec to 200msec seems to help (not sure why)
2) Somehow something happens when returning to the GUI thread (using the synchronization context) and accessing the GUI control. If I don't return to the GUI thread, the 2 locks are working fine together...
Thanks everyone!
There's a few problems with your code, and a few... silly things.
First, your Log.StartMessageGeneration doesn't actually produce a log message every second, because you're not awaiting the task returned by Task.Delay - you're basically just creating a thousand timers very quickly (and pointlessly). The log generation is limited only by the Invoke. Using Thread.Sleep is a blocking alternative to Task.Delay if you don't want to use Tasks, await etc. Of course, therein lies your biggest problem - StartMessageGeneration is not asynchronous with respect to the UI thread!
Second, there's little point in using System.Threading.Timer on your form. Instead, just use the windows forms timer - it's entirely on the UI thread so there's no need for marshalling your code back to the UI thread. Since your TimerProcessor doesn't do any CPU work and it only blocks for a very short time, it's the more straight-forward solution.
If you decide to keep using System.Threading.Timer anyway, there's no point in manually dealing with synchronization contexts - just use BeginInvoke on the form; the same way, there's no point in passing the form as an argument to the method, since the method isn't static. this is your form. You can actually see this is the case since you omitted myForm in listOfMessages.Clear() - the two instances are the same, myForm is superfluous.
A simple pause in the debugger will easily tell you where the program is hung - learn to use the debugger well, and it will save you a lot of time. But let's just look at this logically. StartMessageGeneration runs on the UI thread, while System.Threading.Timer uses a thread-pool thread. When the timer locks syncLock, StartMessageGeneration can't enter the same lock, of course - that's fine. But then you Send to the UI thread, and... the UI thread can't do anything, since it's blocked by StartMessageGeneration, which never gives the UI an opportunity to do anything. And StartMessageGeneration can't proceed, because it's waiting on the lock. The only case where this "works" is when StartMessageGeneration runs fast enough to complete before your timer fires (thus freeing the UI thread to do its work) - which is very much possible due to your incorrect use of Task.Delay.
Now let's look on your "hints" with all we know. 1) is simply your bias in measurements. Since you never wait on the Task.Delay in any way, changing the interval does absolutely nothing (with a tiny change in case the delay is zero). 2) of course - that's where your deadlock is. Two pieces of code that depend on a shared resource, while they both require to take posession of another resource. It's a very typical case of a deadlock. Thread 1 is waiting for A to release B, and thread 2 is waiting for B to release A (in this case, A being syncLock and B being the UI thread). When you remove the Send (or replace it with Post), thread 1 no longer has to wait on B, and the deadlock disappears.
There's other things that make writing code like this simpler. There's little point in declaring your own delegate when you can just use Action<string>, for example; using await helps quite a bit when dealing with mixed UI/non-UI code, as well as managing any kind of asynchronous code. You don't need to use event where a simple function will suffice - you can just pass that delegate to a function that needs it if that makes sense, and it may make perfect sense not to allow multiple event handlers to be called. If you decide to keep with the event, at least make sure it conforms to the EventHandler delegate.
To show how your code can be rewritten to be a bit more up-to-date and actually work:
void Main()
{
Application.Run(new LogForm());
}
public static class Log
{
public static async Task GenerateMessagesAsync(Action<string> logEvent,
CancellationToken cancel)
{
for (int i = 0; i < 1000; i++)
{
cancel.ThrowIfCancellationRequested();
logEvent(i.ToString());
await Task.Delay(1000, cancel);
}
}
}
public partial class LogForm : Form
{
private readonly List<string> messages;
private readonly Button btnStart;
private readonly Button btnStop;
private readonly TextBox tbxLog;
private readonly System.Windows.Forms.Timer timer;
public LogForm()
{
messages = new List<string>();
btnStart = new Button { Text = "Start" };
btnStart.Click += btnStart_Click;
Controls.Add(btnStart);
btnStop =
new Button { Text = "Stop", Location = new Point(80, 0), Enabled = false };
Controls.Add(btnStop);
tbxLog = new TextBox { Height = 200, Multiline = true, Dock = DockStyle.Bottom };
Controls.Add(tbxLog);
timer = new System.Windows.Forms.Timer { Interval = 500 };
timer.Tick += TimerProcessor;
timer.Start();
}
private void TimerProcessor(object sender, EventArgs e)
{
foreach (var message in messages)
{
tbxLog.AppendText(message + Environment.NewLine);
}
messages.Clear();
}
private async void btnStart_Click(object sender, EventArgs e)
{
btnStart.Enabled = false;
var cts = new CancellationTokenSource();
EventHandler stopAction = (_, __) => cts.Cancel();
btnStop.Click += stopAction;
btnStop.Enabled = true;
try
{
await Log.GenerateMessagesAsync(message => messages.Add(message), cts.Token);
}
catch (TaskCanceledException)
{
messages.Add("Cancelled.");
}
finally
{
btnStart.Enabled = true;
btnStop.Click -= stopAction;
btnStop.Enabled = false;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
timer.Dispose();
btnStart.Dispose();
btnStop.Dispose();
tbxLog.Dispose();
}
base.Dispose(disposing);
}
}
SynchronizationContext.Send is run synchronously. When you call it, you actually block the UI thread until the operation is complete. But if UI thread is already in lock state, then it just make sense that you are in deadlock.
You can use SynchronizationContext.Post to avoid this.
I just answer on your question, but the truth is that your code need a "little" refactoring..
This is a follow up question to Updating a dialog from another form (The code and screenshots can be found there)
To solve my GUI hanging problem I received 2 recommendations:
Using Application.DoEvents()
Using a BackgroundWorker
The DoEvents() approach works, however it has been pointed out that I should not use it. Indeed, I notice that the GUI updates correctly but is unresponsive for short times.
That's why I want to use a BackgroundWorker and have read up on it.
I don't understand how I would implement it so that it can be used to update the 4 labels in my example code separately, though.
I want to show the progress (and update 4 dialog labels) as the program successfully finishes one job. The BackgroundWorker has only 1 DoWork() though. I have tried to use the e.Argument of the DoWorkEventArgs to differentiate between the different update methods but that attempt had failed.
public partial class BackgroundWorkerImportStatusDialog : Form
{
private BackgroundWorker dialogWorker = new BackgroundWorker();
private string path;
private string clientName;
public BackgroundWorkerImportStatusDialog()
{
InitializeComponent();
}
public void updateFileStatus(string path)
{
this.path = path;
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.FileStatus);
}
public void updatePrintStatus()
{
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.PrintStatus);
}
public void updateImportStatus(string clientName)
{
this.clientName = clientName;
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.ImportStatus);
}
public void updateArchiveStatus()
{
dialogWorker = new BackgroundWorker();
dialogWorker.DoWork += new DoWorkEventHandler(updateLabels);
dialogWorker.RunWorkerAsync(UpdateComponent.ArchiveStatus);
}
private void updateLabels(object sender, DoWorkEventArgs e)
{
MessageBox.Show(e.Argument.ToString());
if ((UpdateComponent) e.Argument == UpdateComponent.FileStatus)
{
t_filename.Text = path;
}
if ((UpdateComponent) e.Argument == UpdateComponent.PrintStatus)
{
t_printed.Text = "sent to printer";
}
if ((UpdateComponent) e.Argument == UpdateComponent.ImportStatus)
{
t_client.Text = clientName;
}
if ((UpdateComponent) e.Argument == UpdateComponent.ArchiveStatus)
{
t_archived.Text = "archived";
}
}
public enum UpdateComponent { FileStatus, PrintStatus, ImportStatus, ArchiveStatus}
And I can't imagine having 4 BackgroundWorkers for this pretty trivial dialog is the solution.
As I understand your question, you want to have your dialog form inform the user about 4 different aspects of your application running:
printing status
file status
import status
archiver status
Background worker could be used to periodically check each one. You may advanced progressbar by 25% after status of each operation is checked (and update your UI with appropriate information).
You may also try async programming - i.e. just start the operation, and lets your application continue. When the operation completes, your application will be notified, and could update information on the form.
Depending on the .NET framework you're using you may use async and await (avaialble since .NET 4.5 / C# 5 - async & await on MSDN) or classic approach to asynchronous programming.
Edit:
I am not sure that BackgroundWorker is the best solution in this situation. I can imagine having something like:
BackhgroundWorker checking things just once - i.e. check printing status once, file status once, import status once, archiver status once. This may sound silly, but it could be user behavior driver - i.e. explicitly launched when user clicks or invokes this mechanism any other way. ProgressBar could be put on the application's statausbar, so that user knows that 'application is actually doing something'.
Previous approach could be improved a bit - you never actually finish your job in BackgroundWorker - instead inside your main method you just have an infinite loop. This will allow you to check things periodically. In this approach there is no point in increasing the progress.
Sample for the second approach:
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
CheckPrintingStatus();
CheckFileStatus();
CheckImportStatus();
CheckArchiverStatus();
System.Threading.Thread.Sleep(5000); // sleep for 5 seconds
}
}
}
There is a question if this solution (second approach) is better than having a thread created explicitly. You could think of creating 4 different threads, so that each could check something else. This would be a bit heavier on the OS, but on the other hand you can set different sleep times for every operation.
If you go for bare threads - you may want to use ThreadPool instead of creating threads explicitly.
Im trying to update a progress bar while doing some data type checks on a separate thread and there seems to be a delay between what value the progress bar is at and the value which is actually show.
The following code is executed by the non-GUI thread and is used to raise the event.
protected virtual void OnUpdateProgressBar(object sender, ProgressBarEventArgs e)
{
EventHandler<ProgressBarEventArgs> TempHandler = UpdateProgressBar;
//Avoid possible race condition.
if (TempHandler != null)
{
TempHandler(this, e);
}
}
I have created a separate class for updating the progress bar and when i create an instance of it, i pass a reference to the progress bar. Below is the entire class.
public class ProgressBarChanged
{
ProgressBar statusBar;
public ProgressBarChanged(ProgressBar pb)
{
statusBar = pb;
statusBar.Value = 0;
}
public ProgressBarChanged()
{
}
public void subscribeToEvent(DataVerification test)
{
test.UpdateProgressBar += new EventHandler<ProgressBarEventArgs>(incrementPB);
}
public void incrementPB(object sender, ProgressBarEventArgs e)
{
Action action = () =>
{
if (e.CurrentRow == e.FinalRow - 10)
{
int i = 5;
}
statusBar.Maximum = e.FinalRow;
statusBar.Value = e.CurrentRow;
};
if(statusBar.InvokeRequired)
statusBar.Invoke(action);
else
action();
}
}
I have uploaded a screen shot showing the progress bar and the actual values.
Any ideas???
Thanks
The progessbar is a simple feedback to the user, not a piece of exact instrumentation. It's a pacifier.
It also incorporates it's own async logic to update the screen (independent of the message loop). This makes that it may run a little behind.
What's the big deal?
To get more accurate results, divide your range into < 100 segments and do fewer updates.
A delay is pretty normal. After all, invoking a method in the UI thread means Windows will dispatch a message and if your thread is fast enough (and CPU consuming) then it'll appear faster than UI.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace testThreads
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
textBox1.Text = "Counting to 10000000, value is " + i + Environment.NewLine;
}
}
public void countToZero()
{
for (int i = 10000000; i > 0; i--)
{
textBox2.Text = "Counting to 0, value is " + i + Environment.NewLine;
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread countUp = new Thread(new ThreadStart(countToLots));
Thread countDown = new Thread(new ThreadStart(countToZero));
countUp.Start();
countDown.Start();
}
private void button2_Click(object sender, EventArgs e)
{
textBox3.Text = "Bobby bob bob " + Environment.NewLine;
}
}
}
I really need to try and get the hang of this - i just dont understand the theory behind why i get an error message. Could someone help me out please?
Cross-thread operation not valid:
Control 'textBox1' accessed from a
thread other than the thread it was
created on.
UI controls have "thread affinity"; they do not want to be touched by anything except the UI thread; that includes reading and writing properties. The assignment to .Text should be done from the UI thread, either by using Invoke, or BackgroundWorker.
For example:
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
// running on bg thread
textBox1.Invoke((MethodInvoker) delegate {
// running on UI thread
textBox1.Text = "Counting to 10000000, value is "
+ i + Environment.NewLine;
});
// running on bg thread again
}
}
But note that this type of thread switching has overhead. You should not call back every iteration - you should (for example) update the UI every [n] iterations - in the above, every 10000 for example.
You cannot use a method or property of a Form control from a different thread than the thread that created (called new) the control.
To do that just do:
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
SetText("Counting to 10000000, value is " + i + Environment.NewLine);
}
}
public void SetText(string text)
{
if (this.textBox1.InvokeRequired())
{
Action<string> auxDelegate = SetText;
this.BeginInvoke(auxDelegate,text);
}
else
{
this.textBox1.Text = text;
}
}
What the method is doing with the beginInvoke is just calling again the SetText method from the thread that created the control.
Ok, about the theory behind WHY controls have UI Thread affinity.
If you've programmed long enough you would remember the days when forms and rapid application development were not the standard. In those days just droping a control into a form was rare... everything was done by the old school.
Now, in windows, the "old school" way of doing things involved defining a WindowProc.
The WindowProc is a function which is invoked to handle application message (notice I say is, not was). This function runs on the main program thread and is in charge of handling every message the application receives, including user interface paint and refresh.
Nowadays all that is mostly automated so that when you create a form the code in charge of doing all the work is autogenerated and you don't have to worry about that... but it is still there.
Of course, if the thread in charge of drawing the user interface with all its controls, is the main thread, you'll see how changing things from another thread might disturb the application itself with race conditions and so on. In addition, since the UI handling is autogenerated you can't just put the synchronization mechanisms that you'll use with two standard threads because you only have access to code on one thread, yours, but not to the main windowproc callback.
In a way, what BeginInvoke will do for you is pass a message to the main thread telling it to kindly handle the delegate in her own context when the time is right, thus delegating the execution to the main thread.
I am developing an application in C# using National Instruments Daqmx for performing measurements on certain hardware.
My setup consists of several detectors from which I have to get data during a set period of time, all the while updating my UI with this data.
public class APD : IDevice
{
// Some members and properties go here, removed for clarity.
public event EventHandler ErrorOccurred;
public event EventHandler NewCountsAvailable;
// Constructor
public APD(
string __sBoardID,
string __sPulseGenCtr,
string __sPulseGenTimeBase,
string __sPulseGenTrigger,
string __sAPDTTLCounter,
string __sAPDInputLine)
{
// Removed for clarity.
}
private void APDReadCallback(IAsyncResult __iaresResult)
{
try
{
if (this.m_daqtskRunningTask == __iaresResult.AsyncState)
{
// Get back the values read.
UInt32[] _ui32Values = this.m_rdrCountReader.EndReadMultiSampleUInt32(__iaresResult);
// Do some processing here!
if (NewCountsAvailable != null)
{
NewCountsAvailable(this, new EventArgs());
}
// Read again only if we did not yet read all pixels.
if (this.m_dTotalCountsRead != this.m_iPixelsToRead)
{
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
else
{
// Removed for clarity.
}
}
}
catch (DaqException exception)
{
// Removed for clarity.
}
}
private void SetupAPDCountAndTiming(double __dBinTimeMilisec, int __iSteps)
{
// Do some things to prepare hardware.
}
public void StartAPDAcquisition(double __dBinTimeMilisec, int __iSteps)
{
this.m_bIsDone = false;
// Prepare all necessary tasks.
this.SetupAPDCountAndTiming(__dBinTimeMilisec, __iSteps);
// Removed for clarity.
// Begin reading asynchronously on the task. We always read all available counts.
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
public void Stop()
{
// Removed for clarity.
}
}
The object representing the detector basically calls a BeginXXX operation with a callback that holds the EndXXX en also fires an event indicating data available.
I have up to 4 of these detector objects as members of my UI form. I call the Start() method on all of them in sequence to start my measurement. This works and the NewCountsAvailable event fires for all four of them.
Due to the nature of my implementation, the BeginXXX method is called on the UI thread and the Callback and the Event are also on this UI thread. Therefore I cannot use some kind of while loop inside my UI thread to constantly update my UI with the new data because the events constantly fire (I tried this). I also do not want to use some kind of UpdateUI() method in each of the four NewCountsAvailable eventhandlers since this will load my system too much.
Since I am new to threaded programming in C# I am now stuck;
1) What is the "proper" way to handle a situation like this?
2) Is my implementation of the detector object sound? Should I call the Start() methods on these four detector objects from yet another thread?
3) Could I use a timer to update my UI every few hundred miliseconds, irrespective of what the 4 detector objects are doing?
I really have no clue!
I'd use a simple deferred update system.
1) Worker threads signal "data ready" by raising an event
2) UI thread listens for the event. When it is received, it just sets a "data needs updating" flag and returns, so minimal processing occurs on the event itself.
3) UI thread uses a timer (or sits on Application.Idle events) to check the "data needs updating" flag and, if necessary, update the UI. In many cases, UI only needs to be updated once or twice a second, so this need not burn a lot of CPU time.
This allows the UI to continue running as normal all the time (remaining interactive for the user), but within a short period of some data being ready, it is displayed in the UI.
Additionally, and most importantly for good UI, this approach can be used to allow multiple "data ready" events to fire and be rolled into a single UI update. This means that if 10 pieces of data are completed in close succession, the UI updates once rather than your window flickering for several seconds as the UI redraws (unnecessarily) 10 times.
I would try moving the IDevice monitoring logic to seperate threads for each device. The UI can then poll for values via a timer event, button click or some other UI related event. That way your UI will remain responsive and your threads are doing all the heavy lifting. Here's a basic example of this using a continuous loop. Obviously, this is a brutally simple example.
public partial class Form1 : Form
{
int count;
Thread t = null;
public Form1()
{
InitializeComponent();
}
private void ProcessLogic()
{
//CPU intensive loop, if this were in the main thread
//UI hangs...
while (true)
{
count++;
}
}
private void Form1_Load(object sender, EventArgs e)
{
//Cannot directly call ProcessLogic, hangs UI thread.
//ProcessLogic();
//instead, run it in another thread and poll needed values
//see button1_Click
t = new Thread(ProcessLogic);
t.Start();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
t.Abort();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = count.ToString();
}
}
Some updates to reflect the new data you've provided:
Although I have my doubts that your EndXXX methods are happening on the UI thread, I still think you should spawn off the work to a background thread and then update the UI either as events are fired or as needed.
Because you've added a tight while loop in your UI, you need to call Application.DoEvents to allow your other events to be called.
Here's an updated sample that shows results in the UI as they occur:
public class NewCountArgs : EventArgs
{
public NewCountArgs(int count)
{
Count = count;
}
public int Count
{
get; protected set;
}
}
public class ADP
{
public event EventHandler<NewCountArgs> NewCountsAvailable;
private double _interval;
private double _steps;
private Thread _backgroundThread;
public void StartAcquisition(double interval, double steps)
{
_interval = interval;
_steps = steps;
// other setup work
_backgroundThread = new Thread(new ThreadStart(StartBackgroundWork));
_backgroundThread.Start();
}
private void StartBackgroundWork()
{
// setup async calls on this thread
m_rdrCountReader.BeginReadMultiSampleUInt32(-1, Callback, _steps);
}
private void Callback(IAsyncResult result)
{
int counts = 0;
// read counts from result....
// raise event for caller
if (NewCountsAvailable != null)
{
NewCountsAvailable(this, new NewCountArgs(counts));
}
}
}
public class Form1 : Form
{
private ADP _adp1;
private TextBox txtOutput; // shows updates as they occur
delegate void SetCountDelegate(int count);
public Form1()
{
InitializeComponent(); // assume txtOutput initialized here
}
public void btnStart_Click(object sender, EventArgs e)
{
_adp1 = new ADP( .... );
_adp1.NewCountsAvailable += NewCountsAvailable;
_adp1.StartAcquisition(....);
while(!_adp1.IsDone)
{
Thread.Sleep(100);
// your NewCountsAvailable callbacks will queue up
// and will need to be processed
Application.DoEvents();
}
// final work here
}
// this event handler will be called from a background thread
private void NewCountsAvailable(object sender, NewCountArgs newCounts)
{
// don't update the UI here, let a thread-aware method do it
SetNewCounts(newCounts.Count);
}
private void SetNewCounts(int counts)
{
// if the current thread isn't the UI thread
if (txtOutput.IsInvokeRequired)
{
// create a delegate for this method and push it to the UI thread
SetCountDelegate d = new SetCountDelegate(SetNewCounts);
this.Invoke(d, new object[] { counts });
}
else
{
// update the UI
txtOutput.Text += String.Format("{0} - Count Value: {1}", DateTime.Now, counts);
}
}
}
I don't know if I fully understand. What if you update you an object that contains the current data. So the callback don't directly interact with the UI. Then you could update the UI at a fixed rate, e.g. n times per second from another thread. See this post on updating UI from a background thread. I am assuming that you are using Windows Forms and not WPF.
The B* * *dy captcha system decided it was a good idea to lose my answer I spent half an hour typing without so much as a warning or a chance to correct... so here we go again:
public class APD : IDevice
{
// Some members and properties go here, removed for clarity.
public event EventHandler ErrorOccurred;
public event EventHandler NewCountsAvailable;
public UInt32[] BufferedCounts
{
// Get for the _ui32Values returned by the EndReadMultiSampleUInt32()
// after they were appended to a list. BufferdCounts therefore supplies
// all values read during the experiment.
}
public bool IsDone
{
// This gets set when a preset number of counts is read by the hardware or when
// Stop() is called.
}
// Constructor
public APD( some parameters )
{
// Removed for clarity.
}
private void APDReadCallback(IAsyncResult __iaresResult)
{
try
{
if (this.m_daqtskRunningTask == __iaresResult.AsyncState)
{
// Get back the values read.
UInt32[] _ui32Values = this.m_rdrCountReader.EndReadMultiSampleUInt32(__iaresResult);
// Do some processing here!
if (NewCountsAvailable != null)
{
NewCountsAvailable(this, new EventArgs());
}
// Read again only if we did not yet read all pixels.
if (this.m_dTotalCountsRead != this.m_iPixelsToRead)
{
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
else
{
// Removed for clarity.
}
}
}
catch (DaqException exception)
{
// Removed for clarity.
}
}
private void SetupAPDCountAndTiming(double __dBinTimeMilisec, int __iSteps)
{
// Do some things to prepare hardware.
}
public void StartAPDAcquisition(double __dBinTimeMilisec, int __iSteps)
{
this.m_bIsDone = false;
// Prepare all necessary tasks.
this.SetupAPDCountAndTiming(__dBinTimeMilisec, __iSteps);
// Removed for clarity.
// Begin reading asynchronously on the task. We always read all available counts.
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
public void Stop()
{
// Removed for clarity.
}
}
Note I added some things I mistakenly left out in the original post.
Now on my form I have code like this;
public partial class Form1 : Form
{
private APD m_APD1;
private APD m_APD2;
private APD m_APD3;
private APD m_APD4;
private DataDocument m_Document;
public Form1()
{
InitializeComponent();
}
private void Button1_Click()
{
this.m_APD1 = new APD( ... ); // times four for all APD's
this.m_APD1.NewCountsAvailable += new EventHandler(m_APD1_NewCountsAvailable); // times 4 again...
this.m_APD1.StartAPDAcquisition( ... );
this.m_APD2.StartAPDAcquisition( ... );
this.m_APD3.StartAPDAcquisition( ... );
this.m_APD4.StartAPDAcquisition( ... );
while (!this.m_APD1.IsDone) // Actually I have to check all 4
{
Thread.Sleep(200);
UpdateUI();
}
// Some more code after the measurement is done.
}
private void m_APD1_NewCountsAvailable(object sender, EventArgs e)
{
this.m_document.Append(this.m_APD1.BufferedCounts);
}
private void UpdateUI()
{
// use the data contained in this.m_Document to fill the UI.
}
}
phew, I hope I dod not forget anything yping this a second time (that'll teach me not copying it before hitting Post).
What I see running this code is that;
1) The APD object works as advertised, it measures.
2) The NewCountsAvailable events fire and their handlers get executed
3) APD.StartAPDAcquisition() is called on the UI thread. Thus also BeginXXX is called on this thread. Therefore, by design, the callback is also on this thread and obviously also the NewCountsAvailable eventhandlers run on the UI thread. The only thing that is not on the UI thread is waiting for the hardware to return values to the BeginXXX EndXXX pair of calls.
4) Because the NewCountsAvailable events fire quite a lot, the while loop I intended to use for updating the UI does not run. Typically it runs once in the beginning and then somehow gets interupted by the eventhandlers that need to process. I do not fully understand this though, but it does not work...
I was thinking to solve this by getting rid of the while loop and putting a Forms.Timer on the form where UpdateUI() would be called from the Tick eventhandler. However, I do not know if this would be deemed "best practice". I also do not know if all these eventhandlers will eventually bring the UI thread to a crawl, I might need to add a few more of these APD objects in the future. Also UpdateUI() might contain some heavier code for calculating an image based on the values in m_Document. So the tick eventhandler might also be a resource drain in the timer approach. In case I use this solution I would also need to have a "Done" event in my APD class to notify when each APD finishes.
Should I perhaps not be working with events for notifying that new counts are available but instead work with some kind of "on demand" reading of APD.BufferedCounts and put the whole thing in yet another thread? I really haven't a clue...
I basically need a clean, lightweight solution that scales well should I add yet more APD's :)