internal void HandleCompleted() { if (this.IsCompletedInternally && (System.Threading.Interlocked.Exchange(ref this.userNotified, 1) == 0)) { this.abortable = null; try { if (!Util.DoNotHandleException(this.Failure)) { this.CompletedRequest(); } } catch (Exception ex) { if (this.HandleFailure(ex)) { throw; } } finally { this.userCompleted = true; this.SetAsyncWaitHandle(); if ((null != this.userCallback) && !(this.Failure is System.Threading.ThreadAbortException) && !(this.Failure is System.StackOverflowException)) { this.userCallback(this); } } } }
/// <summary>Set the AsyncWait and invoke the user callback.</summary> /// <remarks> /// If the background thread gets a ThreadAbort, the userCallback will never be invoked. /// This is why it's generally important to never wait forever, but to have more specific /// time limit. Also then cancel the operation, to make sure its stopped, to avoid /// multi-threading if your wait time limit was just too short. /// </remarks> internal void HandleCompleted() { // Dev10 if (this.IsCompletedInternally && (System.Threading.Interlocked.Exchange(ref this.userNotified, 1) == 0)) { this.abortable = null; // reset abort via CancelRequest try { // avoid additional work when aborting for exceptional reasons if (!Util.DoNotHandleException(this.Failure)) { // the CompleteRequest may do additional work which is why // it is important not to signal the user via either the // IAsyncResult.IsCompleted, IAsyncResult.WaitHandle or the callback this.CompletedRequest(); } } catch (Exception ex) { if (this.HandleFailure(ex)) { throw; } } finally { // 1. set IAsyncResult.IsCompleted, otherwise user was // signalled on another thread, but the property may not be true. this.userCompleted = true; // 2. signal the wait handle because it can't be first nor can it be last. // // There is a very small window for race condition between setting the wait handle here and disposing // the wait handle inside EndExecute(). Say thread1 is the async thread that executes up till this point, i.e. right // after userCompleted is set to true and before the asyncWait is signaled; thread2 wakes up and calls EndExecute() till // right before we try to dispose the wait handle; thread3 wakes up and calls AsyncWaitHandle which creates a new instance // for this.asyncWait; thread2 then resumes to dispose this.asyncWait and if at this point thread1 sets this.asyncWait, // we'll get an ObjectDisposedException on thread1. SetAsyncWaitHandle() will protect this scenario with a critical section. this.SetAsyncWaitHandle(); // 3. invoke the callback because user may throw an exception and stop any further processing if ((null != this.userCallback) && !(this.Failure is System.Threading.ThreadAbortException) && !(this.Failure is System.StackOverflowException)) { // any exception thrown by user should be "unhandled" // it's possible callback will be invoked while another creates and sets the asyncWait this.userCallback(this); } } } }
internal bool HandleFailure(Exception e) { System.Threading.Interlocked.CompareExchange(ref this.failure, e, null); this.SetCompleted(); return(Util.DoNotHandleException(e)); }