Re: design Q : using timer/threads
From: Joseph M. Newcomer (newcomer_at_flounder.com)
Date: 10/22/04
- Next message: Joseph M. Newcomer: "Re: Question about virtual"
- Previous message: Sergey Kochkarev: "Re: C++ help needed!(My own solution)"
- In reply to: Joan Coleman: "design Q : using timer/threads"
- Next in thread: Joan Coleman: "RE: design Q : using timer/threads"
- Messages sorted by: [ date ] [ thread ]
Date: Fri, 22 Oct 2004 03:10:03 -0400
The first thing that jumps out is you say "the program (dialog) goes into a loop waiting
for the user to interact via FKEYS". At this point, you are so far lost that there is no
recovery. Get rid of all loop variables. Throw the code out and start over from the
beginning.
Create a WM_KEYDOWN handler that detects the function key you want; the loop is built into
the MFC message loop.
void CMyDialog::OnKeyDown(...)
{
switch(key)
{
case VK_F1:
Create worker thread
Create disk writer thread
return;
case VK_F2:
Set flag indicating shutdown
return;
}
CDialog::OnKeyDown(...);
}
That's it. That's all you need to do.
Read my essay on worker threads on my MVP Tips site. You can put all the variables in the
dialog as member variables and access them from the worker thread. You will probably need
synchronization (a CRITICAL_SECTION would be the most likely solution) to control access
to the variables, but the question arises as to why you would need to access them from the
thread at all. Unless time is super-critical, you can use PostThreadMessage to simply send
a message to the main GUI thread to do the updating. This avoids any need for
synchronization (I'm currently controlling an embedded mass spectrometer using a technique
similar to this; it is a device with some real-time constraints)
Timer callbacks at the 1ms level or even the 2ms level will impact system performance.
Generally, trying to coerce Windows into supporting devices with these types of
constraints should be viewed as a nearly-doomed effort; the scheduler will eventually
cause you to miss your timing window, possibly by a fairly large margin.
The comment "start threading to write to disk" is completely wrong. The disk-write thread
must be created outside the realtime callback; you don't want to spend that kind of effort
inside the callback itself. And all you want to do is queue the request up for output. You
could consider doing asynchronous I/O, posting a thread message to a UI thread that
contains the data to be written, or posting a message to an I/O completion port to let a
thread of your own do simple synchronous file I/O. You should not even be thinking about
"updating buffering controls"; the buffering should be implicit. Typically you don't lose
a lot of performance doing a simple new operation, but if you think this is going to be a
problem--and it could be---then preallocating a pool of buffers could improve performance,
but you then have to decide what you will do if you run out of buffers.
It is not clear why you need to poll for the keys in the callback at all, or worry about
anything like "write final buffer" there. When the time comes to stop the loop, just set a
flag and stop the data collection. Then, using your buffer management structure, do
whatever you have implemented to support a "flush" operation.
You can use MFC stuff in threads perfectly well as long as you understand where
synchronization needs to be done, but doing I/O in a 1ms callback is probably a bad idea.
In fact, I can't figure out why the callback is needed at all. If worker thread 1 is
collecting data, there is no need to have a callback routine at all!
worker thread 1:
while(running)
{
read some data
call buffer-write routine
}
call the buffer-shutdown handler routine
buffer-write routine
write data into buffer until buffer is full
if buffer full, enqueue I/O operation, allocate new buffer
buffer-shutdown routine
enqueue I/O operation to write partial buffer
enqueue request to shut down writer thread
I/O worker thread
while(true)
{
get message from queue
if shutdown message, break
else perform I/O operation synchronously
}
Note that no polling is involved, no timer callbacks are required, no funny loops that
violate GUI design standards are required, and the code is amazingly short and
straightforward. In fact, note that no synchronization is required! No thread works on a
shared structure, so there is no need to do any synchronization.
I also need to graph the incoming data on the screen (as well as write it to a file), so I
simply post a thread message to the main GUI thread indicating a new data point was
delivered. In my essay on I/O completion ports, I show how to use these in the OnIdle
handler to minimize the impact on the GUI of the posting of massive numbers of messages.
This would be a different I/O completion port than the one used as the queue for disk I/O
operations.
You have taken a very, very simple problem and made it needlessly complex. Lose the timer
callback, lose the loops, create a worker thread to read from the device and queue data up
for a slower write-to-disk thread, and write a trivial write-to-disk thread that does
simple synchronous I/O after dequeueing the request.
Oh yes, selecting F1 is a Really Bad Idea, since this is reserved to mean "invoke help",
and that is what users expect.
joe
On Thu, 21 Oct 2004 12:41:54 -0700, Joan Coleman <JoanColeman@discussions.microsoft.com>
wrote:
>Hi All
>
>I have not used multimedia timers nor threads in an MFC program as yet. I
>have searched through the newsgroups and done a lot of reading (incl. MVP
>sites - which are very helpful, thanks) and have come up with a design for my
>application. I am including the pseudo code and would really appreciate any
>suggestions regarding whether it will work. I do not want to start coding and
>find out I got something really stupid in here. If this is too much to ask,
>then perhaps some pointers as to what might be done better. TIA for all help.
>
>The task ( a "real time" data acquisition system) is as follows: at some
>point, the program (dialog) goes into a loop waiting on the user to interact
>via FKEYS; when the user enters FKEY1 the data collection process starts up;
>another FKEY1 press stops the data collection. (this can occur a lot of
>times). When the user is completely finished, via FKEY 8,the data acquisition
>part of the program is over .When data collection is not active, the user can
>interact via other function keys. When data collection is active, the user
>cannot interact, except by depressing FKEY 1 and by triggering an external
>event button.
>
>I am using VC 6.0 on a Windows XP Pro system - 3 GHz machine with dual
>processors. The program is dialog based; the dialog which is running this
>piece of code is a modal dialog started from the main dialog.
>
>Data Collection code:
>
>loop1 = loop2 = true;
>Start worker thread 1; //polls a network for tracking data and stores in
>dialog vars
>while (loop1)
> while(loop2)
> FKEY1 //start run
> setup multimedia timer (1 millisec) with callback function
> loop2 = false;
> endrun = false;
> FKEY2:
> FKEY3:
> .
> .
> FKEY8: //end DAQ
> loop2 = false;
> loop1 = false;
> stop worker thread 1;
> end loop2;
> update the dialog display; (via control variables)
> if endrun
> stop multimedia timer;
> loop2 = true;
> end if
>end loop1;
>
>
> the CALLBACK handler:
>
>CallBack
> fetch some instrument readings;
> build record containing A/D, tracking info, etc. and move to buffer
> check on event input;
> if FKEY 1 pressed
> write final buffer
> endrun = true
> else
> if buffer full
> start thread to write to disk;
> update buffering controls;
> end if
> end if
>end callback
>
>A couple assumptions/questions:
>endrun needs to be volatile
>the tracking data needs some sort of lock/unlock control governing its access.
>A specific question: how should other variables be declared so that the
>threads/callback procedure may access them - as private vars in the header
>section of the class?????
>Also, can I write to the disk (the file is a CFile object) in a thread, or
>should it be moved back in the loop1 code? (think I read somewwhere not to
>use MFC stuff in threads).
>
>Believe I also read somewhere that a 2 ms interrupt won't impact the system
>overhead too badly, but that 1ms will. Would it be better to put the
>callback code into a loop to do it twice, and use a 2 ms interrupt?
>
>
>
>
Joseph M. Newcomer [MVP]
email: newcomer@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
- Next message: Joseph M. Newcomer: "Re: Question about virtual"
- Previous message: Sergey Kochkarev: "Re: C++ help needed!(My own solution)"
- In reply to: Joan Coleman: "design Q : using timer/threads"
- Next in thread: Joan Coleman: "RE: design Q : using timer/threads"
- Messages sorted by: [ date ] [ thread ]