异步结果句柄返回给调用者

时间:2009-08-13 20:37:52

标签: c# asynchronous

我有一个方法可以将一些工作排​​队,以便异步执行。我想向调用者返回一些句柄,可以轮询,等待或用于从操作中获取返回值,但我找不到适合该任务的类或接口。

BackgroundWorker很接近,但它适用于工作者有自己的专用线程的情况,在我的情况下不是这样。 IAsyncResult看起来很有希望,但提供的AsyncResult实现对我来说也无法使用。我应该自己实施IAsyncResult吗?

澄清

我有一个概念上看起来像这样的课程:

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);
        }
    }
}

QueueAsyncWork函数将工作项推送到轮询列表中,以获取专用工作线程,其中只有一个工作线程。我的问题不在于编写QueueAsyncWork函数 - 这很好。我的问题是,我该如何回复来电者? SomeHandle应该是什么?

现有的.Net类适用于异步操作可以封装在返回的单个方法调用中的情况。这不是这里的情况 - 所有工作对象都在同一个线程上完成工作,完整的工作操作可能跨越多次调用workObject.DoSomeWork()。在这种情况下,为调度程序提供进度通知,完成和获取操作的最终结果的一些合理方法是什么?

5 个答案:

答案 0 :(得分:1)

如果我理解正确,您有一组工作对象(IWorkObject),每个工作对象通过多次调用DoSomeWork方法完成任务。当IWorkObject对象完成其工作时,您希望以某种方式对此做出响应,并且在此过程中您想要响应任何报告的进度?

在这种情况下,我建议你采取稍微不同的方法。您可以查看Parallel Extension frameworkblog)。使用框架,您可以编写如下内容:

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

有些注意事项:

  • QueueWork现在返回void。原因是报告进度或任务完成时发生的操作已成为执行工作的线程的一部分。您当然可以返回工厂创建的Task并从方法返回(例如,启用轮询)。
  • 进度报告和完成处理现在是线程的一部分,因为您应该尽可能避免轮询。轮询更加昂贵,因为通常你要么过于频繁地进行轮询(太早),要么不经常进行轮询(太迟)。没有理由不能在运行任务的线程内报告任务的进度和完成情况。
  • 上述内容也可以使用(较低级别)ThreadPool.QueueUserWorkItem方法实现。

使用QueueUserWorkItem

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

答案 1 :(得分:1)

WorkObject类可以包含需要跟踪的属性。

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;
   }
}

然后在你的例子中:

  • 将集合从List更改为可以保存Guid值的Dictionary(或任何其他唯一标识值的方法)。
  • 通过让调用方传递从 QueueAsyncWork 收到的Guid来公开正确的WorkObject属性。

我假设您将异步启动 WorkThread (尽管是唯一的异步线程);另外,您必须检索字典值和WorkObject属性是否是线程安全的。

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;
}

答案 2 :(得分:1)

是的,实施IAsyncResult(或者更确切地说,是它的扩展版本,以提供进度报告)。

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();
            }
        }
    }
}

答案 3 :(得分:0)

最简单的方法是here。假设您有一个方法string DoSomeWork(int)。然后,您可以创建正确类型的委托,例如:

Func<int, string> myDelegate = DoSomeWork;

然后在代理上调用BeginInvoke方法:

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

一旦异步调用完成,将调用Callback委托。您可以按如下方式定义此方法:

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

使用所描述的方案,您还可以轮询结果或等待它们。请查看我提供的网址以获取更多信息。

此致 罗纳德

答案 4 :(得分:0)

如果您不想使用异步回调,可以使用显式WaitHandle,例如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();
    }
}

在您的代码中,您可以说

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

不要忘记在workObject上调用Dispose。

你总是可以使用为每个工作对象创建这样的包装的替代实现,并且在WaitForCompletion()中调用_waitHandle.Dispose(),你可以懒惰地实例化等待句柄(注意:前面的竞争条件)等。 (这几乎就是BeginInvoke为代表所做的事情。)