|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
AsyncResult, Calback and WaitHandlesI have a web service that reads data from the database in a number of
asynchronous calls. I have a callback for each call so that I can call EndInvoke so that I can trap any errors. I also have a WaitHandle.WaitAll. The trouble is that the WaitAll completes before the callbacks are all complete. So the web service returns results despite the fact that one of the database calls errored. How do I trap the error and bubble it up to the original web service thread? Thanks Phil If I understand this correctly, you use the WaitAll to block the main thread
until the asynchronous calls are done. But it unblocks before the async callbacks have a chance to execute. Under the covers, each async process signals its associated Event object prior to calling its callback routine. Thus the fatal flaw in your existing design: The Event objects are (by design) signalled before the callbacks are delivered. I would use a SyncLock (lock in C#) block to arbitrate access to a private variable that keeps track of the progress of asynchronous work. When the final async callback completes, you can use whatever private mechanism you like best to wake up or queue a call onto the main thread. The main point here is that you need to implement your own private synchronization mechanism. When dealing with async code, don't try to use both the WaitHandle and the Callback. It's an either/or proposition. - Bob <d***@2bytes.co.uk> wrote in message Show quote news:1172855054.523482.137240@v33g2000cwv.googlegroups.com... >I have a web service that reads data from the database in a number of > asynchronous calls. > > I have a callback for each call so that I can call EndInvoke so that I > can trap any errors. > > I also have a WaitHandle.WaitAll. > > The trouble is that the WaitAll completes before the callbacks are all > complete. So the web service returns results despite the fact that one > of the database calls errored. > > How do I trap the error and bubble it up to the original web service > thread? > > Thanks > Phil > thanks for this Bob. Am starting to understand now. So if the main
thread needs results from the async process then a callback is not going to be any good to me if that main thread is a web service method because by the time the callback comes the main thread might have gone? So I will use Wait and then do the EndInvoke after that. I need to figure out how WaitAny works because I've tried that but am not quite sure how you determine which waithandle to process.... I could use WaitAll but WaitAny seems more efficient. Also, just to check regarding the callback, if I was using, for example a Windows app, and wanted to, as you put it, wake up the main thread or queue a call, how would I go about doing that> Do you have any sample code that shows how the worker thread communicates back to the main thread? thanks a lot for this. Show quote On Mar 2, 8:49 pm, "Bob Altman" <r...@nospam.nospam> wrote: > If I understand this correctly, you use the WaitAll to block the main thread > until the asynchronous calls are done. But it unblocks before the async > callbacks have a chance to execute. > > Under the covers, each async process signals its associated Event object > prior to calling its callback routine. Thus the fatal flaw in your existing > design: The Event objects are (by design) signalled before the callbacks > are delivered. > > I would use a SyncLock (lock in C#) block to arbitrate access to a private > variable that keeps track of the progress of asynchronous work. When the > final async callback completes, you can use whatever private mechanism you > like best to wake up or queue a call onto the main thread. > > The main point here is that you need to implement your own private > synchronization mechanism. When dealing with async code, don't try to use > both the WaitHandle and the Callback. It's an either/or proposition. > > - Bob > > <d***@2bytes.co.uk> wrote in message > > news:1172855054.523482.137240@v33g2000cwv.googlegroups.com... > > >I have a web service that reads data from the database in a number of > > asynchronous calls. > > > I have a callback for each call so that I can call EndInvoke so that I > > can trap any errors. > > > I also have a WaitHandle.WaitAll. > > > The trouble is that the WaitAll completes before the callbacks are all > > complete. So the web service returns results despite the fact that one > > of the database calls errored. > > > How do I trap the error and bubble it up to the original web service > > thread? > > > Thanks > > Phil I have only a passing familiarity with Web Services, but I don't see why
that should stop me from offering advice. ;-) If your web service wants to kick off some asynchronous processing, then it seems to me that you have several options: 1. You can block the web service thread until the async operations have completed. You would want to do this if, for example, it takes less time to run your async operations in parallel than it would to serialize them (call them them one after another synchronously). In this case, your initialization code would be: private m_myEvent As New Threading.AutoResetEvent(False) Your web service code would be: StartAsyncStuff() m_myEvent.WaitOne() ProcessAsyncOperationResults Your async callback routine would be: Try EndXxx() Catch WhateverException Store exception info in a global variable End Try ' Wake up the main thread If (all async work is done) Then m_myEvent..Set 2. You can start the async processing and immediately return control back to the web method caller. In this case, the caller would be clueless about the results of the async processing. You could use a local logging mechanism (such as the event log) to record any problems that arise from your async processing. 3. You can combine options 1 and 2 by having your routine (let's call it BeginXxx) allocate a state object in a hash table or dictionary with a unique index. BeginXxx would start the async processing, passing the state object to each async routine's "state" argument, then return the unique index to the web method caller. As the async routines complete, BeginXxx would fetch the state object from the IAsyncResult.State member, and store the results in the state object. You would then provide a companion routine (let's call it EndXxx) which takes as an argument the index returned by BeginXxx. EndXxx fetches the state object from the dictionary and blocks (as shown above) until all of the async processing is done. On your last question (how to queue a call to the main thread): If your app is a Windows Forms app, then all of the event handlers that are called in your "code behind the form" are called on the "main UI" thread. Under the covers, this is actually the thread that spends most of its time waiting for the Windows message pump to deliver Windows messages to it. If, for example, you call someStream.BeginWrite() in a button's Click event then the completion callback will happen on a thread pool thread, not on the main UI thread. If you try to access the main form or any of its controls in that callback then you will either get unpredictable results (it may work or you may get some really strange error) in VS 2003, or you will get an exception in VS 2005. The exception to the "don't access the main form or its controls" rule is InvokeRequired, Invoke, and BeginInvoke. I use this design pattern to transfer control from a routine called on a non-UI thread back to the UI thread: Sub SomeRoutine(SomeArgs) If Me.InvokeRequired() Then ' Queue the call to the UI thread Dim deleg As New SomeRoutineDeleg(AddressOf SomeRoutine) Dim args() as Object = {SomeArgs} Me.BeginInvoke(deleg, args) Else ' We're on the UI thread... do the work End If End Sub Good luck... - Bob <d***@2bytes.co.uk> wrote in message Show quote news:1172920525.690835.135760@s48g2000cws.googlegroups.com... > thanks for this Bob. Am starting to understand now. So if the main > thread needs results from the async process then a callback is not > going to be any good to me if that main thread is a web service method > because by the time the callback comes the main thread might have > gone? > > So I will use Wait and then do the EndInvoke after that. I need to > figure out how WaitAny works because I've tried that but am not quite > sure how you determine which waithandle to process.... I could use > WaitAll but WaitAny seems more efficient. > > Also, just to check regarding the callback, if I was using, for > example a Windows app, and wanted to, as you put it, wake up the main > thread or queue a call, how would I go about doing that> Do you have > any sample code that shows how the worker thread communicates back to > the main thread? > > thanks a lot for this. > > On Mar 2, 8:49 pm, "Bob Altman" <r...@nospam.nospam> wrote: >> If I understand this correctly, you use the WaitAll to block the main >> thread >> until the asynchronous calls are done. But it unblocks before the async >> callbacks have a chance to execute. >> >> Under the covers, each async process signals its associated Event object >> prior to calling its callback routine. Thus the fatal flaw in your >> existing >> design: The Event objects are (by design) signalled before the callbacks >> are delivered. >> >> I would use a SyncLock (lock in C#) block to arbitrate access to a >> private >> variable that keeps track of the progress of asynchronous work. When the >> final async callback completes, you can use whatever private mechanism >> you >> like best to wake up or queue a call onto the main thread. >> >> The main point here is that you need to implement your own private >> synchronization mechanism. When dealing with async code, don't try to >> use >> both the WaitHandle and the Callback. It's an either/or proposition.
Show quote
On Mar 4, 1:19 am, "Bob Altman" <r...@nospam.com> wrote: thanks for this. There's something I obviously haven't quite got> I have only a passing familiarity with Web Services, but I don't see why > that should stop me from offering advice. ;-) > > If your web service wants to kick off some asynchronous processing, then it > seems to me that you have several options: > > 1. You can block the web service thread until the async operations have > completed. You would want to do this if, for example, it takes less time to > run your async operations in parallel than it would to serialize them (call > them them one after another synchronously). In this case, your > initialization code would be: > > private m_myEvent As New Threading.AutoResetEvent(False) > > Your web service code would be: > > StartAsyncStuff() > m_myEvent.WaitOne() > ProcessAsyncOperationResults > > Your async callback routine would be: > > Try > EndXxx() > Catch WhateverException > Store exception info in a global variable > End Try > > ' Wake up the main thread > If (all async work is done) Then m_myEvent..Set > > 2. You can start the async processing and immediately return control back to > the web method caller. In this case, the caller would be clueless about the > results of the async processing. You could use a local logging mechanism > (such as the event log) to record any problems that arise from your async > processing. > > 3. You can combine options 1 and 2 by having your routine (let's call it > BeginXxx) allocate a state object in a hash table or dictionary with a > unique index. BeginXxx would start the async processing, passing the state > object to each async routine's "state" argument, then return the unique > index to the web method caller. As the async routines complete, BeginXxx > would fetch the state object from the IAsyncResult.State member, and store > the results in the state object. You would then provide a companion routine > (let's call it EndXxx) which takes as an argument the index returned by > BeginXxx. EndXxx fetches the state object from the dictionary and blocks > (as shown above) until all of the async processing is done. > > On your last question (how to queue a call to the main thread): If your app > is a Windows Forms app, then all of the event handlers that are called in > your "code behind the form" are called on the "main UI" thread. Under the > covers, this is actually the thread that spends most of its time waiting for > the Windows message pump to deliver Windows messages to it. If, for > example, you call someStream.BeginWrite() in a button's Click event then the > completion callback will happen on a thread pool thread, not on the main UI > thread. If you try to access the main form or any of its controls in that > callback then you will either get unpredictable results (it may work or you > may get some really strange error) in VS 2003, or you will get an exception > in VS 2005. The exception to the "don't access the main form or its > controls" rule is InvokeRequired, Invoke, and BeginInvoke. I use this > design pattern to transfer control from a routine called on a non-UI thread > back to the UI thread: > > Sub SomeRoutine(SomeArgs) > If Me.InvokeRequired() Then > ' Queue the call to the UI thread > Dim deleg As New SomeRoutineDeleg(AddressOf SomeRoutine) > Dim args() as Object = {SomeArgs} > Me.BeginInvoke(deleg, args) > Else > ' We're on the UI thread... > do the work > End If > End Sub > > Good luck... > > - Bob > > <d***@2bytes.co.uk> wrote in message > > news:1172920525.690835.135760@s48g2000cws.googlegroups.com... > > > thanks for this Bob. Am starting to understand now. So if the main > > thread needs results from the async process then a callback is not > > going to be any good to me if that main thread is a web service method > > because by the time the callback comes the main thread might have > > gone? > > > So I will use Wait and then do the EndInvoke after that. I need to > > figure out how WaitAny works because I've tried that but am not quite > > sure how you determine which waithandle to process.... I could use > > WaitAll but WaitAny seems more efficient. > > > Also, just to check regarding the callback, if I was using, for > > example a Windows app, and wanted to, as you put it, wake up the main > > thread or queue a call, how would I go about doing that> Do you have > > any sample code that shows how the worker thread communicates back to > > the main thread? > > > thanks a lot for this. > > > On Mar 2, 8:49 pm, "Bob Altman" <r...@nospam.nospam> wrote: > >> If I understand this correctly, you use the WaitAll to block the main > >> thread > >> until the asynchronous calls are done. But it unblocks before the async > >> callbacks have a chance to execute. > > >> Under the covers, each async process signals its associated Event object > >> prior to calling its callback routine. Thus the fatal flaw in your > >> existing > >> design: The Event objects are (by design) signalled before the callbacks > >> are delivered. > > >> I would use a SyncLock (lock in C#) block to arbitrate access to a > >> private > >> variable that keeps track of the progress of asynchronous work. When the > >> final async callback completes, you can use whatever private mechanism > >> you > >> like best to wake up or queue a call onto the main thread. > > >> The main point here is that you need to implement your own private > >> synchronization mechanism. When dealing with async code, don't try to > >> use > >> both the WaitHandle and the Callback. It's an either/or proposition. sussed. To do with WaitAny. If I use SqlCommand.BeginExecuteReader then I can use WaitAny and use the index returned to process the relevant results. If I use a delegate to one of my own functions, use BeginInvoke and then use WaitAny, I keep getting 0 as the WaitHandle index which then causes an error because you can only call EndInvoke once. Do I have to use this AutoResetEvent within the function called? How do I do this? - my function is currently called like this: WaitHandle[] wh = new WaitHandle[2]; accDelegate accDel = new accDelegate(new Account().GetDetails); IAsyncResult res1 = accDel.BeginInvoke(123); ordDelegate ordDel = new ordDelegate(new Order().GetDetails); IAsyncResult res2 = ordDel.BeginInvoke(123); wh[0] = res1.AsyncWaitHandle; wh[1] = res2.AsyncWaitHandle; Hashtable accounts; Hashtable orders; for (int i=0;i<2;i++) { int index = WaitHandle.WaitAny(wh); if (index == WaitHandle.WaitTimeout) throw new Exception("Timeout."); switch(index) { case 0: accounts = accDel.EndInvoke(res1); break; case 1: orders = ordDel.EndInvoke(res2); break; } } carry on........ But I get index returned as 0 twice in a row......
Show quote
> thanks for this. There's something I obviously haven't quite got That's curious. It's behaving as if the first WaitHandle is still signalled > sussed. To do with WaitAny. If I use SqlCommand.BeginExecuteReader > then I can use WaitAny and use the index returned to process the > relevant results. > > If I use a delegate to one of my own functions, use BeginInvoke and > then use WaitAny, I keep getting 0 as the WaitHandle index which then > causes an error because you can only call EndInvoke once. > > Do I have to use this AutoResetEvent within the function called? How > do I do this? - my function is currently called like this: > > WaitHandle[] wh = new WaitHandle[2]; > accDelegate accDel = new accDelegate(new Account().GetDetails); > IAsyncResult res1 = accDel.BeginInvoke(123); > > ordDelegate ordDel = new ordDelegate(new Order().GetDetails); > IAsyncResult res2 = ordDel.BeginInvoke(123); > > wh[0] = res1.AsyncWaitHandle; > wh[1] = res2.AsyncWaitHandle; > > Hashtable accounts; > Hashtable orders; > > for (int i=0;i<2;i++) > { > int index = WaitHandle.WaitAny(wh); > if (index == WaitHandle.WaitTimeout) > throw new Exception("Timeout."); > > switch(index) > { > case 0: > accounts = accDel.EndInvoke(res1); > break; > case 1: > orders = ordDel.EndInvoke(res2); > break; > } > > } > > carry on........ > > > But I get index returned as 0 twice in a row...... the second time around. I would have expected that the delegate object would return a reference to an AutoResetEvent object, which would reset its "signalled" state when your WaitAny() call completes. (BTW, you left off the timeout parameter in the WaitAny call, so you will never see a "TimedOut" result.) Take a look at the Type of the object returned by res1.AsyncWaitHandle. Your other option is to simply call the two EndInvoke routines in lieu of the entire "for" loop: res1 = accDel.BeginInvoke; res2 = ordDel.VeginInvoke; accDel.EndInvoke(res1); ordDel.EndInvoke(res2); This code blocks until both async routines complete, which is the same behavior that your example code exhibits.
Show quote
On 8 Mar, 21:57, "Bob Altman" <r...@nospam.nospam> wrote: Aha, I didn't see your reply until now but today I figured out the> > thanks for this. There's something I obviously haven't quite got > > sussed. To do withWaitAny. If I use SqlCommand.BeginExecuteReader > > then I can useWaitAnyand use the index returned to process the > > relevant results. > > > If I use a delegate to one of my own functions, use BeginInvoke and > > then useWaitAny, I keep getting 0 as the WaitHandle index which then > > causes an error because you can only call EndInvokeonce. > > > Do I have to use this AutoResetEvent within the function called? How > > do I do this? - my function is currently called like this: > > > WaitHandle[] wh = new WaitHandle[2]; > > accDelegate accDel = new accDelegate(new Account().GetDetails); > > IAsyncResult res1 = accDel.BeginInvoke(123); > > > ordDelegate ordDel = new ordDelegate(new Order().GetDetails); > > IAsyncResult res2 = ordDel.BeginInvoke(123); > > > wh[0] = res1.AsyncWaitHandle; > > wh[1] = res2.AsyncWaitHandle; > > > Hashtable accounts; > > Hashtable orders; > > > for (int i=0;i<2;i++) > > { > > int index = WaitHandle.WaitAny(wh); > > if (index == WaitHandle.WaitTimeout) > > throw new Exception("Timeout."); > > > switch(index) > > { > > case 0: > > accounts = accDel.EndInvoke(res1); > > break; > > case 1: > > orders = ordDel.EndInvoke(res2); > > break; > > } > > > } > > > carry on........ > > > But I get index returned as 0 twice in a row...... > > That's curious. It's behaving as if the first WaitHandle is still signalled > the second time around. I would have expected that the delegate object > would return a reference to an AutoResetEvent object, which would reset its > "signalled" state when yourWaitAny() call completes. (BTW, you left off > the timeout parameter in theWaitAnycall, so you will never see a > "TimedOut" result.) > > Take a look at the Type of the object returned by res1.AsyncWaitHandle. > > Your other option is to simply call the two EndInvoke routines in lieu of > the entire "for" loop: > > res1 = accDel.BeginInvoke; > res2 = ordDel.VeginInvoke; > > accDel.EndInvoke(res1); > ordDel.EndInvoke(res2); > > This code blocks until both async routines complete, which is the same > behavior that your example code exhibits. problem anyway. It returns a ManualResetEvent object - don't know why, don't know what determines it - but anyway, when I cast the WaitHandle to a ManualResetEvent and use the Reset method after getting the WaitAny , it all works fine. I write this because after searching for the solution in newsgroups I never found the answer - so maybe this will help someone in the future. thanks |
|||||||||||||||||||||||