views:

404

answers:

5

I have a method that queues some work to be executed asynchronously. I'd like to return some sort of handle to the caller that can be polled, waited on, or used to fetch the return value from the operation, but I can't find a class or interface that's suitable for the task.

BackgroundWorker comes close, but it's geared to the case where the worker has its own dedicated thread, which isn't true in my case. IAsyncResult looks promising, but the provided AsyncResult implementation is also unusable for me. Should I implement IAsyncResult myself?

Clarification:

I have a class that conceptually looks like this:

class AsyncScheduler 
{

    private List<object> _workList = new List<object>();
    private bool _finished = false;

    public SomeHandle QueueAsyncWork(object workObject)
    {
        // simplified for the sake of example
        _workList.Add(workObject);
        return SomeHandle;
    }

    private void WorkThread()
    {
        // simplified for the sake of example
        while (!_finished)
        {
            foreach (object workObject in _workList)
            {
                if (!workObject.IsFinished)
                {
                    workObject.DoSomeWork();
                }
            }
            Thread.Sleep(1000);
        }
    }
}

The QueueAsyncWork function pushes a work item onto the polling list for a dedicated work thread, of which there will only over be one. My problem is not with writing the QueueAsyncWork function--that's fine. My question is, what do I return to the caller? What should SomeHandle be?

The existing .Net classes for this are geared towards the situation where the asynchronous operation can be encapsulated in a single method call that returns. That's not the case here--all of the work objects do their work on the same thread, and a complete work operation might span multiple calls to workObject.DoSomeWork(). In this case, what's a reasonable approach for offering the caller some handle for progress notification, completion, and getting the final outcome of the operation?

A: 

The simplest way to do this is described here. Suppose you have a method string DoSomeWork(int). You then create a delegate of the correct type, for example:

Func<int, string> myDelegate = DoSomeWork;

Then you call the BeginInvoke method on the delegate:

int parameter = 10;
myDelegate.BeginInvoke(parameter, Callback, null);

The Callback delegate will be called once your asynchronous call has completed. You can define this method as follows:

void Callback(IAsyncResult result)
{
    var asyncResult = (AsyncResult) result;
    var @delegate = (Func<int, string>) asyncResult.AsyncDelegate;
    string methodReturnValue = @delegate.EndInvoke(result);
}

Using the described scenario, you can also poll for results or wait on them. Take a look at the url I provided for more info.

Regards, Ronald

Ronald Wildenberg
A: 

If you don't want to use async callbacks, you can use an explicit WaitHandle, such as a ManualResetEvent:

public abstract class WorkObject : IDispose
{
    ManualResetEvent _waitHandle = new ManualResetEvent(false);

    public void DoSomeWork()
    {
        try
        {
            this.DoSomeWorkOverride();
        }
        finally
        {
            _waitHandle.Set();
        }
    }

    protected abstract DoSomeWorkOverride();

    public void WaitForCompletion()
    {
        _waitHandle.WaitOne();
    }

    public void Dispose()
    {
        _waitHandle.Dispose();
    }
}

And in your code you could say

using (var workObject = new SomeConcreteWorkObject())
{
    asyncScheduler.QueueAsyncWork(workObject);
    workObject.WaitForCompletion();
}

Don't forget to call Dispose on your workObject though.

You can always use alternate implementations which create a wrapper like this for every work object, and who call _waitHandle.Dispose() in WaitForCompletion(), you can lazily instantiate the wait handle (careful: race conditions ahead), etc. (That's pretty much what BeginInvoke does for delegates.)

Ruben
+1  A: 

If I understand correctly you have a collection of work objects (IWorkObject) that each complete a task via multiple calls to a DoSomeWork method. When an IWorkObject object has finished its work you'd like to respond to that somehow and during the process you'd like to respond to any reported progress?

In that case I'd suggest you take a slightly different approach. You could take a look at the Parallel Extension framework (blog). Using the framework, you could write something like this:

public void QueueWork(IWorkObject workObject)
{
    Task.TaskFactory.StartNew(() =>
        {
            while (!workObject.Finished)
            {
                int progress = workObject.DoSomeWork();
                DoSomethingWithReportedProgress(workObject, progress);
            }
            WorkObjectIsFinished(workObject);
        });
}

Some things to note:

  • QueueWork now returns void. The reason for this is that the actions that occur when progress is reported or when the task completes have become part of the thread that executes the work. You could of course return the Task that the factory creates and return that from the method (to enable polling for example).
  • The progress-reporting and finish-handling are now part of the thread because you should always avoid polling when possible. Polling is more expensive because usually you either poll too frequently (too early) or not often enough (too late). There is no reason you can't report on the progress and finishing of the task from within the thread that is running the task.
  • The above could also be implemented using the (lower level) ThreadPool.QueueUserWorkItem method.

Using QueueUserWorkItem:

public void QueueWork(IWorkObject workObject)
{
    ThreadPool.QueueUserWorkItem(() =>
        {
            while (!workObject.Finished)
            {
                int progress = workObject.DoSomeWork();
                DoSomethingWithReportedProgress(workObject, progress);
            }
            WorkObjectIsFinished(workObject);
        });
}
Ronald Wildenberg
+1  A: 

The WorkObject class can contain the properties that need to be tracked.

public class WorkObject
{
   public PercentComplete { get; private set; }
   public IsFinished { get; private set; }

   public void DoSomeWork()
   {
      // work done here

      this.PercentComplete = 50;

      // some more work done here

      this.PercentComplete = 100;
      this.IsFinished = true;
   }
}

Then in your example:

  • Change the collection from a List to a Dictionary that can hold Guid values (or any other means of uniquely identifying the value).
  • Expose the correct WorkObject's properties by having the caller pass the Guid that it received from QueueAsyncWork.

I'm assuming that you'll start WorkThread asynchronously (albeit, the only asynchronous thread); plus, you'll have to make retrieving the dictionary values and WorkObject properties thread-safe.

private Dictionary<Guid, WorkObject> _workList = 
   new Dictionary<Guid, WorkObject>();

private bool _finished = false;

public Guid QueueAsyncWork(WorkObject workObject)
{
    Guid guid = Guid.NewGuid();
    // simplified for the sake of example
    _workList.Add(guid, workObject);
    return guid;
}

private void WorkThread()
{
    // simplified for the sake of example
    while (!_finished)
    {
        foreach (WorkObject workObject in _workList)
        {
            if (!workObject.IsFinished)
            {
                workObject.DoSomeWork();
            }
        }
        Thread.Sleep(1000);
    }
}

// an example of getting the WorkObject's property
public int GetPercentComplete(Guid guid)
{
   WorkObject workObject = null;
   if (!_workList.TryGetValue(guid, out workObject)
      throw new Exception("Unable to find Guid");

   return workObject.PercentComplete;
}
JHBlues76
In this case, why return `Guid` instead of the `WorkObject` itself?
Alexey Romanov
Yes, you could make QueueAsyncWork void in this example and the caller could directly check the queued objects properties. This would also eliminate the need to keep a dictionary. I guess this would depend on if the Work Object's progress tracking properties should be directly exposed to the caller, or perhaps they can even be wrapped in another class.
JHBlues76
+1  A: 

Yes, implement IAsyncResult (or rather, an extended version of it, to provide for progress reporting).

public class WorkObjectHandle : IAsyncResult, IDisposable
{
    private int _percentComplete;
    private ManualResetEvent _waitHandle;
    public int PercentComplete {
        get {return _percentComplete;} 
        set 
        {
            if (value < 0 || value > 100) throw new InvalidArgumentException("Percent complete should be between 0 and 100");
            if (_percentComplete = 100) throw new InvalidOperationException("Already complete");
            if (value == 100 && Complete != null) Complete(this, new CompleteArgs(WorkObject));
            _percentComplete = value;
        } 
    public IWorkObject WorkObject {get; private set;}
    public object AsyncState {get {return WorkObject;}}
    public bool IsCompleted {get {return _percentComplete == 100;}}
    public event EventHandler<CompleteArgs> Complete; // CompleteArgs in a usual pattern
    // you may also want to have Progress event
    public bool CompletedSynchronously {get {return false;}}
    public WaitHandle
    {
        get
        {
            // initialize it lazily
            if (_waitHandle == null)
            {
                ManualResetEvent newWaitHandle = new ManualResetEvent(false);
                if (Interlocked.CompareExchange(ref _waitHandle, newWaitHandle, null) != null)
                    newWaitHandle.Dispose();
            }
            return _waitHandle;
        }
    }

    public void Dispose() 
    {
         if (_waitHandle != null)
             _waitHandle.Dispose();
         // dispose _workObject too, if needed
    }

    public WorkObjectHandle(IWorkObject workObject) 
    {
        WorkObject = workObject;
        _percentComplete = 0;
    }
}

public class AsyncScheduler 
{
    private Queue<WorkObjectHandle> _workQueue = new Queue<WorkObjectHandle>();
    private bool _finished = false;

    public WorkObjectHandle QueueAsyncWork(IWorkObject workObject)
    {
        var handle = new WorkObjectHandle(workObject);
        lock(_workQueue) 
        {
            _workQueue.Enqueue(handle);
        }
        return handle;
    }

    private void WorkThread()
    {
        // simplified for the sake of example
        while (!_finished)
        {
            WorkObjectHandle handle;
            lock(_workQueue) 
            {
                if (_workQueue.Count == 0) break;
                handle = _workQueue.Dequeue();
            }
            try
            {
                var workObject = handle.WorkObject;
                // do whatever you want with workObject, set handle.PercentCompleted, etc.
            }
            finally
            {
                handle.Dispose();
            }
        }
    }
}
Alexey Romanov