2009-08-13 22 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機能は、専用の作業スレッドのポーリングリスト上に作業項目をプッシュし、そこになるの:

私は概念的にはこのようになりますクラスを持っていますただ1つに過ぎない。私の問題は、QueueAsyncWork関数を書くことではない - それは問題ありません。私の質問は、私は呼び出し元に何を返すのですか? SomeHandleはどうすればよいですか?

このための既存の.Netクラスは、非同期操作を返す単一のメソッド呼び出しでカプセル化できる状況に対応しています。これはここには当てはまりません。すべての作業オブジェクトは同じスレッド上で作業を行い、完全な作業操作はworkObject.DoSomeWork()への複数の呼び出しにまたがることがあります。この場合、呼び出し元に進捗通知、完了、および操作の最終結果を得るための処理を提供する合理的なアプローチは何ですか?

+0

'提供されているAsyncResult実装も私にとっては使用できません' ...説明できますか? –

+0

あなたは今何を意味しているのか分かります。より良い答えを出そうとしています。 DoSomeWorkの内部には、タスクの進捗状況があると思いますか? –

+0

好奇心を要して、なぜあなたのワーカー・メソッドはすべて同じスレッドで実行する必要がありますか?私は多くの非同期作業を行いましたが、以前はその要件を見ていませんでした。 –

答えて

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

このシナリオを使用して、結果をポーリングするか、それらを待機させることもできます。私が詳細情報を提供したURLを見てください。

よろしく、 ロナルド

0

あなたは非同期コールバックを使用したくない場合は、あなたがそのようなManualResetEventとして、明示的なWaitHandleを使用することができます。

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がデリゲートにとって何を行うのかとほとんど同じです。)

1

私が正しく理解している場合は、それぞれがDoSomeWorkメソッドを複数回呼び出すことによってタスクを完了する作業オブジェクト(IWorkObject)のコレクションがあります。 IWorkObjectオブジェクトの作業が終了したら、何らかの形でそれに対応したいと思っています。プロセス中に報告された進捗状況に対応したいのですか?

その場合は、少し違うアプローチをとることをお勧めします。 Parallel Extension frameworkblog)をご覧ください。今voidを返し

  • QueueWork

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

    注意すべきいくつかの点:フレームワークを使用して、あなたはこのような何かを書くことができます。これは、進行状況が報告されたときやタスクが完了したときに発生するアクションが、そのタスクを実行するスレッドの一部になったためです。もちろん、ファクトリが作成してメソッドから返されるTaskを返すことができます(たとえばポーリングを有効にする)。

  • 可能であれば、常にポーリングを避ける必要があるため、進行状況レポートと終了処理がスレッドの一部になりました。ポーリングは、通常、あまりにも頻繁に(早すぎる)、頻繁に(あまりにも遅く)ポーリングするので、より高価です。タスクを実行しているスレッド内からタスクの進行状況と終了を報告できない理由はありません。
  • 上記は、(下位レベル)ThreadPool.QueueUserWorkItemメソッドを使用して実装することもできます。 QueueUserWorkItemを使用して

public void QueueWork(IWorkObject workObject) 
{ 
    ThreadPool.QueueUserWorkItem(() => 
     { 
      while (!workObject.Finished) 
      { 
       int progress = workObject.DoSomeWork(); 
       DoSomethingWithReportedProgress(workObject, progress); 
      } 
      WorkObjectIsFinished(workObject); 
     }); 
} 
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; 
    } 
} 

  • 変更のGUID値(または一意の値を識別する任意の他の手段)を保持することができる辞書のリストから収集。
  • 呼び出し元が受け取ったGuidを、QueueAsyncWorkから渡すようにして、正しい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

この場合、 'WorkObject'自体の代わりに' Guid'を返すのはなぜですか? –

+0

はい、この例ではQueueAsyncWorkをvoidにすることができ、呼び出し側はキューに入れられたオブジェクトのプロパティを直接チェックできます。これにより、辞書を保持する必要もなくなります。私は、ワークオブジェクトの進行状況の追跡プロパティを呼び出し元に直接公開するか、別のクラスでラップすることさえできるかどうかに依存していると思います。 – JHBlues76

関連する問題