Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

Setting global variables in Asynchronous callback methods in C#

Hi

 

I am having some problems with assigning data to global variables inside of asynchronous callback methods.  I have been wrestling with this problem for a couple of days now and I still haven’t got an answer to my satisfaction.

 

Problem:

 

 
 
ON TO next post ... no more than 5000 characters
0 Kudos
Message 1 of 9
(10,200 Views)

Problem:

 

I wish to read various channels (both analog and digital and output a pulse using the on of the counter channels) using my PXI-6254 card.  I have created a class library that gets called to setup the tasks and do the reading.  Any other class can then use the appropriate method to start up an operation and read in the data; probably a window form will do the calling as a synchronizing object (type: System.ComponentModel.ISynchronizeInvoke, in this case it will be the instance of the windows form that it calling the library method) is need for the tasks if using begin (i.e. BeginReadMultiSamplePortInt16) and end read (EndReadMultiSamplePortInt16) methods.  I am using begin and end read methods instead of read (i.e. ReadMultiSamplePortInt16) methods as I want to sample both analog and digital data at the same time. Thus the reading procedure goes a little like this:

 

  1. Create the various task, set the same synchronizing object for all tasks, then start the tasks

  2. Call the begin reading methods and get back the System.IAsyncResult object, use this to determine if the reading has finished (i.e. IsCompleted)

  3. In the asynchronous callback method get the data using end read and assign it to a private global variable.  Note: I have used lock(this) before I assign the data read in to the global variable. 

this.analogData =  this.myAnalogMultiChannelReader.EndReadMultiSample(ar);     à returns a double[,] 

  1. Back in the method that setup the begin read, it realizes that the data has been read in and it continues to the next statement which then attempts to access the data.  

     

 

0 Kudos
Message 2 of 9
(10,194 Views)

Step 4 is where things go wrong.  It seems that the first time you access the global variable after it has been set in the asynchronous method, the dimensions that it returns are all 1 for the matrix; however the second access is okay.  I don’t really know what is going on here.  I know that the asynchronous callback is called on a separate thread and thus that may be causing the problem.  Is there a way to solve this problem or do I just make a bogus first access to the global variables and then make a second access and use that data?

 

I have attached a file with the source code. 

 

Thank you

 

Regards,

 

Elnaz

 
Source still to come...
0 Kudos
Message 3 of 9
(10,193 Views)

Note: the file class_library.cs is a cut down version of my actual library.  I have also added the file GlobalData.cs, which are my base data structures and MessageBoxEx.cs which is a timed messagebox; which I have used in my code.  I have put all source code into one file.  I couldn't attach more than one file to this post and I couldn't attached a winrar file.  Please search for the

#region Class Library, #region DataStructures, #region MessageBoxEx  statements to distinguish between the files

Smiley Indifferent Long process

0 Kudos
Message 4 of 9
(10,199 Views)

Hi Elnaz,

I took a look at your code, and also wrote a quick C# application to see if I could reproduce the results you are experiencing.  Sure enough, by setting breakpoints in my code I found that although the IsCompleted property of the IAsyncResult object is getting set to true, and the callback is getting called, the order is backwards from what you expect.  It appears to me that the IsCompleted property is an indication that the hardware has finished acquisition of the samples you requested, but it is not a guarantee that the asynchronous callback method has executed and completed.  In other words, calling EndReadMultiSample on the reader does not set the IAsyncResult's IsCompleted property to true; the property is simply an indication that the hardware has finished acquisition.  If you wish to stall your main thread until the callback has finished execution then you should probably set your own bool value to true in the callback yourself to indicate that the callback finished.

This will fix your problem, but it seems to me, from looking at your source code, that you really don't need to perform an asynchronous read at all.  Your code basically calls a non-blocking method (the asynchronous read) and then manually blocks until the data is read.  It would be much simpler to use the built-in synchronous read, by calling ReadMultiSample, instead.  This will guarantee that your data is returned before you try to process it, and your callback functions will no longer be necessary.  Typically asynchronous reads are used in continuous acquisition or generation tasks where you need to constantly acquire data in addition to doing extra processing in the meantime, such as updating the UI or performing data analysis.  Since you are acquiring a finite number of samples only once, it doesn't seem necessary to use asynchronous reads in your application.

Hexar Anderson
Measurement Studio Staff Software Engineer
National Instruments
0 Kudos
Message 5 of 9
(10,164 Views)
Upon further reflection, I disagree with almost everything I previously said in the second paragraph of my response.  I assume the reason you are using asynchronous reads is because you want to simultaneously read from both your analog input and digital input tasks.  I still have a few comments on your implementation as it currently stands.
 
First, the callbacks you are using in the calls to BeginReadMultiSample are not necessary; instead of creating a new AsyncCallback for the callback methods, you can pass null instead.  If you pass null for the callback, then you have to wait for IsCompleted and then call EndReadMultiSample yourself in the main method.
 
Second, it's better to call Sleep() than to have a no-op inside of your while loop, for better thread utilization on your machine.
 
In summary, you can use the following code to replace what you are currently doing:
 
 
digitalReaderAsyncResult = myDigitalMCReader.BeginReadMultiSamplePortInt32(samplesPerChannelToRead, null, null);
analogReaderAsyncResult = myAnalogMCReader.BeginReadMultiSample(samplesPerChannelToRead, null, null);
 
// Logic might be more clear this way, but is essentially the same
while (digitalReaderAsyncResult.IsCompleted == false || analogReaderAsyncResult.IsCompleted == false)
{
     System.Threading.Thread.Sleep(1);
}
 
digitalData = myDigitalMCReader.EndReadMultiSamplePortInt32(digitalReaderAsyncResult);
analogData = myAnalogMCReader.EndReadMultiSample(analogReaderAsyncResult);
 
 

Message Edited by Hexar on 10-12-2005 03:51 PM

Hexar Anderson
Measurement Studio Staff Software Engineer
National Instruments
0 Kudos
Message 6 of 9
(10,164 Views)
While Hexar's approach will work, there's also another way to do this, which can give you even better thread utilization, which is to use the WaitHandle class. Your code would look like this:
 
digitalReaderAsynchronousCall = myDigitalMCReader.BeginReadMultiSamplePortInt32(samplesPerChannelToRead,null, null); analogReaderAsynchronousCall= myAnalogMCReader.BeginReadMultiSample(samplesPerChannelToRead, null,null);
WaitHandle.WaitAll(new WaitHandle[]{analogReaderAsynchronousCall.AsyncWaitHandle, digitalReaderAsynchronousCall.AsyncWaitHandle});
 
//calls to EndRead<> go here.

(Note that you'll need to add "using System.Threading" to the top of your code for that WaitHandle call to compile as written.)

This approach has a few benefits over explicitly looping over the "IsCompleted" properties:

1) WaitAll puts the thread to sleep completely. The operating system wakes the thread up when the wait handles passed in are signalled. So while it's waiting, your main thread is taking up 0 processor time.

2) There are additional WaitHandle methods for other kinds of synchronization scenarios - WaitAll requires all of the handles to signal before it returns, but you can also use WaitAny (only one of the handles must signal) or WaitOne (called on just one instance of the WaitHandle class to wait for just that one handle to signal.)

3) All of those methods provide overloads that take a timeout parameter - so you can opt to wait only for a certain amount of time for the async operations to complete.

I also noticed something wrong in your code I thought I'd bring to your attention:

In several places in your code, you have something like:

someObject.Equals(null);

This does not set the object to null. "Equals" is a method that compares two objects to determine if they are logically equal. I believe what you want is:

someObject = null;

0 Kudos
Message 7 of 9
(10,153 Views)

Hey all,

Thanks for all your help.  I had seen the waitOne method before but not the waitAll method.  I think it was lack of direction that I didn't go down that path.  Oh well, you live and you learn.  I think both methods will work but I think I will implement Glenn's method as it has better process utilisation. 

Oh, Glenn, thanks for pointing out the:

someObject.Equals(null);

I didn't realise what I had done until you pointed it out.  It is amazing how we subconsciously do some things.

I will try it out and then let you know (after my morning coffee Smiley Happy).

 

Elnaz

0 Kudos
Message 8 of 9
(10,132 Views)

Hey all,

I tried it out and Glenn's method throws an exception:

    "WaitAll for multiple handles on an STA thread is not supported"

So I used Hexar's method and it worked like a charm; while it doesn't have the processor utilisation that Glenn's would, it works.  I had a little think about the exception that got thrown and googled it and had a brief look and I don't think there is a way around it.  Or there might be and I just don't know. 

Well thank you everyone for you help.

Until next time. Smiley Very Happy

Elnaz

0 Kudos
Message 9 of 9
(10,124 Views)