2012-01-05 7 views
4

Commandパターンで非同期操作を処理する最良の方法について意見を聞きたいと思います。C#でのコマンドパターンと非同期操作の処理

public class MyCommand 
{ 
    // Sets up receiver and does whatever stuff 

    public void Execute() 
    { 
     _myReceiver.DoSomething(); 
    } 
} 

問題がされています:MyCommandはMyReceiver.DoSomethingは()のコードの非同期部分を持っているかどうかわからない私たちは、次の例を持っていると言います。実行後にMyCommandを元に戻すスタックにプッシュしたい場合、MyCommandが元に戻すことが可能な状態に達したかどうかを知ることが不確かなため、そのレシーバーアクションが完全に実行されたことを保証できませんでした。

私は個人的には、以下のソリューションに考えた:

  1. がMyReceiver内のイベントを含める
  2. は、コマンドに「BeginExecute」と「EndExecute」が含まれるコマンドの状態制御のいくつかの並べ替えを実装し、コマンドをサブスクライブします

    012:彼らに物事をラップするために、MyCommandに変わるでしょう

(それは私には臭いと思われます)

public class MyCommand 
{ 
    public MyCommand(MyReceiver receiver) 
    { 
     _myReceiver = receiver; 
     _myReceiver.DoSomethingFinished +=() => this.EndExecute(); 
    } 

    public void BeginExecute() 
    { 
     this.EnterExecutionState(); 

     _myReceiver.DoSomething(); 
    } 

    public void EndExecute() 
    { 
     this.LeaveExecutionState(); 
    } 

    // State handling related stuff 
} 

コマンドの受信者が何らかのアクションを実行し、アンドゥスタックにプッシュする準備ができていることを確認する手段があります。しかし、イベント・スパムには、非同期操作を含むすべてのReceiverクラスが本当にバグしています。

私はインターネット上でこのトピックについて多くを見出しておらず、さまざまなアプローチを聞きたいと思っています。

OBS:すべての非同期関連のコードを管理するコマンドはオプションではありません:)。

+0

どのようにあなたが_myReceiver.DoSomething();いくつかの非同期プロパティがあります。それが完了するか、完了したことを知るか、非同期動作を制御できる何かを返します。いずれにしても、おそらく_myReceiver.DoSomething()に何か問題があります。あなたの命令ではありません。 – Euphoric

+0

非同期を除いて、元に戻す部分がどのように見えますか?UndoExecutionまたは...をコマンドしますか? –

+0

@ Humphoric DoSomethingメソッドは.BeginInvoke something ... –

答えて

1

このような何か?

public interface ICommand 
{ 
    void Execute(); 
    event EventHandler Finished; 
} 

public class MyCommand : ICommand 
{ 
    public MyCommand(MyReceiver receiver) 
    { 
     _myReceiver = receiver; 
     _myReceiver.DoSomethingFinished +=() => Finished(); // dont forget null check here. 
    } 

    public void Execute() 
    {  
     _myReceiver.DoSomething(); 
    } 

    public event EventHandler Finished; 
} 

この方法では、このコマンドのユーザーは、コマンドがその非同期動作が終了したとacordingly行動することができたときに知っているように、イベントを完成するために登録することができます。

イベントを使用することを嫌うなら、コールバックはどうですか?

public class MyCommand : ICommand 
{ 
    public MyCommand(MyReceiver receiver) 
    { 
     _myReceiver = receiver; 
    } 

    public void Execute() 
    {  
     _myReceiver.DoSomething(() => Finished()); // dont forget null check here. 
    } 

    public event EventHandler Finished; 
} 

いずれにしても、MyRecieverが呼び出し元に通知して終了した方法が必要です。それをバイパスする方法はありません。

+0

ありがとうございますが、この問題はコマンドの終了を知らせることに関連していません。私は実際にその部分を簡単にするために例から除外しました。私はレシーバーでのイベントの使用が不要なアプローチを見たいと思います。あなたの例では、私はまだ対処することができる各非同期操作のイベントで受信機を迷惑メールする必要があります。 – ferspanghero

+0

@ferspan:MyRecieverと他のすべてに共通の抽象化がない限り、それは同じ方法で使用されますが、可能な解決策はありません。 これは現在のプログラミングでは非常に大きな問題です。非同期、待機中、タスクでこの問題にコミットされた新しいC#バージョンがあります。 – Euphoric

+0

ありがとうございます!あなたのアプローチはTigram'sに似ていました。したがって、私はあなたに同じ質問をします:_myReceiver.DoSomething(アクションアクション)が非同期呼び出しをたくさんすることを考慮すると、すべての非同期呼び出しが完了した後にコールバックが確実に実行されますか? – ferspanghero

1

最初にメソッドAsyncの名前を追加して、メソッドが非同期で実行することを暗黙的にCommandクラスコンシューマに通知します。

第2に、メソッドの非同期呼び出し完了と呼ばれるAction<T>のようなパラメータを追加します。したがって、非同期呼び出しが終了したときに、このメソッド呼び出し側に通知することができます。

編集

obj.DoSomethingAsync(... params, Action<T> onComplete)

+0

正確には私はアクションパラメータを追加しますか? BeginExecuteの場合、_myReceiver.DoSomething()は同じ方法で戻り、非同期状態についてはわかりませんので、無駄になります。それが_myReveiver.DoSomething()にあれば、私はまだイベントの解決策に固執します。 – ferspanghero

+0

@ferspan:もし私がそのクラスに20の非同期メソッドを持っていて、それらのいくつかを別のものの中で実行したらどうでしょうか?これにどうやって対処しますか?各メソッドは*独自のイベントを定義する必要がありますか?またはイベント自体にメソッド定義が含まれますか? ** Imo **、 'Action 'パターンはクリーナー – Tigran

+0

@ferspan:私の編集した記事を参照してください。 – Tigran

1

制御がExecuteメソッドに戻る前にすべての処理が完了し、呼び出しコードの動作を変更せずに、アクションを実行するという方法を変更する必要がある場合は、

最初に、すべての非同期呼び出しを初期化し、現在のスレッドでブロックして(待機)呼び出しを返します。私は、あなたが気づいているスレッドにあるか、任意のスレッドで返されるかのように、あなたの非同期呼び出しの性質が何であるか分かりませんが、何らかのスレッドを思いつくことができるはずですあなたの問題の同期。

Semaphoreを使用して(現在の非同期メソッドを呼び出した後に)現在のスレッドをブロックし、すべての非同期メソッドが応答を返したときにセマフォを解放してみてください。これにより、非同期呼び出しを「再同期」する効果があります。

他の同期方法を使用することもできますが、セマフォは分かりやすいほどシンプルです。

2

あなたは1つのクラスであまりにも多くのことをやっていると思います。ここでの大きな利点は、あなたが明示的にハンドラに行くすべての依存関係に沿ってドラッグすることなく、クライアントへのあなたのコマンドを配布できるということです

// An immutable command, to be handled in-process. 
// ICommand is a marker interface with no members. 
public class DoSomething : ICommand 
{ 
    public readonly Id; 

    public DoSomething(Guid id) 
    { 
     Id = id; 
    } 
} 

// To be handled out-of-process. 
[AsynchronousCommand] 
public class DoSomethingThatTakesAReallyLongTime : ICommand 
{ 
    public readonly Id; 

    public DoSomethingThatTakesAReallyLongTime(Guid id) 
    { 
     Id = id; 
    } 
} 

// This guy could take any number of dependencies: ISomethingRepository, DbContext, etc. 
// Doesn't matter, but it's probably gonna have dependencies. 
public class DoSomethingHandler : IHandler<DoSomething> 
{ 
    public void Handle(DoSomething command) // IHandler<T>'s only member 
    { 
     // CRUD or call call a domain method 
    } 
} 

public class CommandService : ICommandService 
{ 
    public void Execute(params ICommand[] commands) // ICommandService's only member 
    { 
     foreach(var command in commands) 
     { 
      var handler = GetHandler(command); // Could use your IOC container. 

      if (HasAsyncAttribute()) 
       new Action(() => handler.Handle(command)).BeginInvoke(null, null); 
      else 
       handler.Handle(command); 
     } 
    } 
} 

// Something that might consume these 
public class SomethingController 
{ 
    private readonly ICommandService _commandService; 

    public SomethingController(ICommandService commandService) 
    { 
     _commandService = commandService; 
    } 

    [HttpPost] 
    public void DoSomething(Guid id) 
    { 
     _commandService.Execute(new DoSomething(id)); 
    } 

    [HttpPost] 
    public void DoSomethingThatTakesAReallyLongTime(Guid id) 
    { 
     _commandService.Execute(new DoSomethingThatTakesAReallyLongTime(id)); 
    } 
} 

:私はこのようにそれを打破するでしょう。ハンドラはクライアントに知られるべきではありません。すべてのクライアントが知る必要があるのは、コマンドを送信したことであり、すべてのコマンドは成功すると見なされます。

+0

非常に良い。有難うございます!勝者のためのデコレータ! – trailmax