2009-08-13 106 views
3

我有一個方法將某些工作排隊以異步執行。我想返回調用者的某種句柄,可以輪詢,等待或用於從操作中獲取返回值,但找不到適合該任務的類或接口。返回給調用者的異步結果句柄

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()。在這種情況下,向調用者提供進度通知,完成和獲得操作的最終結果的一些處理的合理方法是什麼?

+0

'提供的AsyncResult實現對我來說也是無法使用的'...你能解釋一下嗎? – 2009-08-14 04:24:11

+0

我明白你的意思了。試圖想出更好的答案。我認爲在DoSomeWork裏面你有一些關於任務進展的概念? – 2009-08-17 18:51:21

+0

出於好奇,爲什麼你的所有工作方法都必須在同一個線程上運行?我做了很多異步工作,而且我之前沒有看到過這個要求。 – 2009-08-17 19:00:20

回答

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

最簡單的方法是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); 
} 

使用所描述的場景,您也可以查詢結果,或等待他們。看看我提供的更多信息。

問候, 羅納德

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。

您可以隨時使用它創建一個這樣的包裝爲每一個工作對象和誰打電話_waitHandle.Dispose()在WaitForCompletion(可選的實施方式),你可以懶洋洋地實例等待句柄(請注意:比賽條件提前)等(這幾乎是BeginInvoke爲代表做的事情。)

1

如果我理解正確,您有一組工作對象(IWorkObject),每個工作對象通過多次調用DoSomeWork方法完成一項任務。當某個對象完成其工作時,您希望以某種方式對此進行響應,並且在此過程中您希望響應任何已報告的進度?

在這種情況下,我建議你採取稍微不同的方法。你可以看看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

的工作對象類可以包含需要跟蹤的特性。

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值(或唯一標識值的任何其他裝置)的字典。
  • 通過讓主叫方通過它從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; 
} 
+0

在這種情況下,爲什麼返回'Guid'而不是'WorkObject'本身? – 2009-08-18 19:43:23

+0

是的,您可以在此示例中使QueueAsyncWork爲空,並且調用方可以直接檢查排隊的對象屬性。這也將消除保存字典的需要。我想這將取決於工作對象的進度跟蹤屬性是否應直接顯示給調用者,或者甚至可以將它們包裝在另一個類中。 – JHBlues76 2009-08-19 14:26:43