OperationQueue now attempts to prevent deep call stack nesting levels by looping when an Operation fired its AsyncEnded event synchronously; the OperationQueue's current operation index is now handled using the Interlocked methods, fixing a possible thread safety issue
git-svn-id: file:///srv/devel/repo-conversion/nusu@170 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
2472c892fb
commit
35b1ca742d
|
@ -18,12 +18,12 @@ License along with this library
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
#if UNITTEST
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NMock2;
|
using NMock2;
|
||||||
|
|
||||||
|
@ -354,6 +354,7 @@ namespace Nuclex.Support.Scheduling {
|
||||||
|
|
||||||
/// <summary>Mock object factory</summary>
|
/// <summary>Mock object factory</summary>
|
||||||
private Mockery mockery;
|
private Mockery mockery;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Nuclex.Support.Tracking
|
} // namespace Nuclex.Support.Tracking
|
||||||
|
|
|
@ -20,6 +20,7 @@ License along with this library
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
using Nuclex.Support.Tracking;
|
using Nuclex.Support.Tracking;
|
||||||
|
|
||||||
|
@ -125,24 +126,29 @@ namespace Nuclex.Support.Scheduling {
|
||||||
/// and launches the operation by calling its Start() method.
|
/// and launches the operation by calling its Start() method.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void startCurrentOperation() {
|
private void startCurrentOperation() {
|
||||||
OperationType operation = this.children[this.currentOperationIndex].Transaction;
|
do {
|
||||||
|
Thread.MemoryBarrier();
|
||||||
|
OperationType operation = this.children[this.currentOperationIndex].Transaction;
|
||||||
|
|
||||||
operation.AsyncEnded += this.asyncOperationEndedDelegate;
|
operation.AsyncEnded += this.asyncOperationEndedDelegate;
|
||||||
|
|
||||||
IProgressReporter progressReporter = operation as IProgressReporter;
|
IProgressReporter progressReporter = operation as IProgressReporter;
|
||||||
if(progressReporter != null)
|
if(progressReporter != null)
|
||||||
progressReporter.AsyncProgressChanged += this.asyncOperationProgressChangedDelegate;
|
progressReporter.AsyncProgressChanged += this.asyncOperationProgressChangedDelegate;
|
||||||
|
|
||||||
operation.Start();
|
Interlocked.Exchange(ref this.completionStatus, 1);
|
||||||
|
operation.Start();
|
||||||
|
} while(Interlocked.Decrement(ref this.completionStatus) > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Disconnects from the current operation and calls its End() method</summary>
|
/// <summary>Disconnects from the current operation and calls its End() method</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This unsubscribes the queue from the current operation's events, calls End()
|
/// This unsubscribes the queue from the current operation's events, calls End()
|
||||||
/// on the operation and, if the operation didn't have an exception to report,
|
/// on the operation and, if the operation didn't have an exception to report,
|
||||||
/// counts up the accumulated progress of the queue.
|
/// counts up the accumulated progress of th e queue.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void endCurrentOperation() {
|
private void endCurrentOperation() {
|
||||||
|
Thread.MemoryBarrier();
|
||||||
OperationType operation = this.children[this.currentOperationIndex].Transaction;
|
OperationType operation = this.children[this.currentOperationIndex].Transaction;
|
||||||
|
|
||||||
// Disconnect from the operation's events
|
// Disconnect from the operation's events
|
||||||
|
@ -177,15 +183,16 @@ namespace Nuclex.Support.Scheduling {
|
||||||
|
|
||||||
// Only jump to the next operation if no exception occured
|
// Only jump to the next operation if no exception occured
|
||||||
if(this.exception == null) {
|
if(this.exception == null) {
|
||||||
|
int newIndex = Interlocked.Increment(ref this.currentOperationIndex);
|
||||||
++this.currentOperationIndex;
|
Thread.MemoryBarrier();
|
||||||
|
|
||||||
// Execute the next operation unless we reached the end of the queue
|
// Execute the next operation unless we reached the end of the queue
|
||||||
if(this.currentOperationIndex < this.children.Count) {
|
if(newIndex < this.children.Count) {
|
||||||
startCurrentOperation();
|
if(Interlocked.Increment(ref this.completionStatus) == 1) {
|
||||||
|
startCurrentOperation();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either an exception has occured or we reached the end of the operation
|
// Either an exception has occured or we reached the end of the operation
|
||||||
|
@ -225,6 +232,8 @@ namespace Nuclex.Support.Scheduling {
|
||||||
private float completedWeight;
|
private float completedWeight;
|
||||||
/// <summary>Index of the operation currently executing</summary>
|
/// <summary>Index of the operation currently executing</summary>
|
||||||
private int currentOperationIndex;
|
private int currentOperationIndex;
|
||||||
|
/// <summary>Used to detect when an operation completes synchronously</summary>
|
||||||
|
private int completionStatus;
|
||||||
/// <summary>Exception that has occured in the background process</summary>
|
/// <summary>Exception that has occured in the background process</summary>
|
||||||
private volatile Exception exception;
|
private volatile Exception exception;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user