代码之家  ›  专栏  ›  技术社区  ›  JSBÕ±Õ¸Õ£Õ¹

异步结果句柄以返回到调用方

  •  3
  • JSBÕ±Õ¸Õ£Õ¹  · 技术社区  · 15 年前

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

    BackgroundWorker很接近,但它是针对这样一种情况的,即Worker有自己的专用线程,这在我的情况下是不正确的。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 回复  |  直到 15 年前
        1
  •  1
  •   Alexey Romanov    15 年前

    是的,实现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();
                }
            }
        }
    }
    
        2
  •  1
  •   Ronald Wildenberg    15 年前

    如果我理解正确,你有一个工作对象集合( IWorkObject )每个人都通过多次调用 DoSomeWork 方法。当一个 iWorkObjor 对象已经完成了它的工作,您希望以某种方式对此作出响应,并且在这个过程中,您希望对任何报告的进度作出响应?

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

    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);
            });
    }
    
        3
  •  1
  •   JHBlues76    15 年前

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

    然后在您的示例中:

    • 将集合从列表更改为可以保存guid值(或唯一标识该值的任何其他方法)的字典。
    • 通过让调用者传递从中接收到的GUID来公开正确的工作对象属性 队列工作 .

    我假设你会开始 工作线程 异步(尽管是唯一的异步线程);另外,您还必须确保检索字典值和工作对象属性是线程安全的。

    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;
    }
    
        4
  •  0
  •   Ronald Wildenberg    15 年前

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

    Func<int, string> myDelegate = DoSomeWork;
    

    然后你打电话给 BeginInvoke 委托的方法:

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

    异步调用完成后,将调用回调委托。您可以如下定义此方法:

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

    使用所描述的场景,您还可以轮询结果或等待结果。查看我提供的URL以了解更多信息。

    当做, 罗纳德

        5
  •  0
  •   Ruben    15 年前

    如果不想使用异步回调,可以使用显式WaitHandle,例如ManualReseteEvent:

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

    不过,不要忘记对您的工作对象调用Dispose。

    您总是可以使用替代的实现来为每个工作对象创建这样的包装器,以及在waitforcompletion()中调用wait handle.dispose()的人,您可以延迟地实例化wait handle(小心:前面的竞争条件),等等(这几乎是BeginInvoke为委托所做的)。