2011-10-27 27 views
2

私はLineWriterとAsyncLineWriterという2つのクラスを持っています。私の目標は、呼び出し元がAsyncLineWriterのメソッドで複数の保留中の非同期呼び出しをキューに入れることができるようにすることですが、カバーの下では本当にそれらを並行して実行することはできません。すなわち、何らかの形でキューに入れてお互いを待たなければならない。それでおしまい。この質問の残りの部分は完全な例を示していますので、私が本当に求めていることについて絶対的にあいまいさはありません。C#非同期操作を強制的に同期させる方法

LineWriterを実行するのに約5秒かかる単一の同期方法(WriteLineメソッド)を有する:

public class LineWriter 
{ 
    public void WriteLine(string line) 
    { 
      Console.WriteLine("1:" + line); 
      Thread.Sleep(1000); 
      Console.WriteLine("2:" + line); 
      Thread.Sleep(1000); 
      Console.WriteLine("3:" + line); 
      Thread.Sleep(1000); 
      Console.WriteLine("4:" + line); 
      Thread.Sleep(1000); 
      Console.WriteLine("5:" + line); 
      Thread.Sleep(1000); 
    } 
} 

AsyncLineWriterだけLineWriterをカプセル化し、非同期インターフェイス(BeginWriteLineとEndWriteLine)を提供する:

public class AsyncLineWriter 
{ 
    public AsyncLineWriter() 
    { 
     // Async Stuff 
     m_LineWriter = new LineWriter(); 
     DoWriteLine = new WriteLineDelegate(m_LineWriter.WriteLine); 
     // Locking Stuff 
     m_Lock = new object(); 
    } 

#region Async Stuff 

    private LineWriter m_LineWriter; 
    private delegate void WriteLineDelegate(string line); 
    private WriteLineDelegate DoWriteLine; 
    public IAsyncResult BeginWriteLine(string line, AsyncCallback callback, object state) 
    { 
     EnterLock(); 
     return DoWriteLine.BeginInvoke(line, callback, state); 
    } 
    public void EndWriteLine(IAsyncResult result) 
    { 
     DoWriteLine.EndInvoke(result); 
     ExitLock(); 
    } 

#endregion 

#region Locking Stuff 

    private object m_Lock; 
    private void EnterLock() 
    { 
     Monitor.Enter(m_Lock); 
     Console.WriteLine("----EnterLock----"); 
    } 
    private void ExitLock() 
    { 
     Console.WriteLine("----ExitLock----"); 
     Monitor.Exit(m_Lock); 
    } 

#endregion 

} 

私が最初の段落で述べたように、私の目標は一度に1つの保留中の非同期操作だけを許可することです。私は本当にのように私はここでロックを使用する必要はなかった場合、つまり、BeginWriteLineがIAsyncResultハンドルを返すことができ、常に即座に返され、期待される動作を保持していれば、それは素晴らしいでしょうが、それを行う方法を理解できません。だから私が何をしているのかを説明する次善の方法は、ロックを使うことだけだった。

まだ、私の「ロックスタッフ」セクションが期待どおりに機能していません。私は上記のコード(非同期操作が始まる前にEnterLockを実行し、非同期操作が終了した後にはExitLockを実行する)が、一度に1つの保留中の非同期操作を実行できることを期待しています。私は次のコードを実行した場合

static void Main(string[] args) 
{ 
    AsyncLineWriter writer = new AsyncLineWriter(); 

    var aresult = writer.BeginWriteLine("atest", null, null); 
    var bresult = writer.BeginWriteLine("btest", null, null); 
    var cresult = writer.BeginWriteLine("ctest", null, null); 

    writer.EndWriteLine(aresult); 
    writer.EndWriteLine(bresult); 
    writer.EndWriteLine(cresult); 

    Console.WriteLine("----Done----"); 
    Console.ReadLine(); 
} 

を私は次の出力を参照するを期待

----EnterLock---- 
1:atest 
2:atest 
3:atest 
4:atest 
5:atest 
----ExitLock---- 
----EnterLock---- 
1:btest 
2:btest 
3:btest 
4:btest 
5:btest 
----ExitLock---- 
----EnterLock---- 
1:ctest 
2:ctest 
3:ctest 
4:ctest 
5:ctest 
----ExitLock---- 
----Done---- 

をしかし、その代わりに、私は、彼らがすべてだ意味し、以下を参照してくださいあたかもロックが効果を持たないかのように並行して実行されます:

----EnterLock---- 
----EnterLock---- 
----EnterLock---- 
1:atest 
1:btest 
1:ctest 
2:atest 
2:btest 
2:ctest 
3:atest 
3:btest 
3:ctest 
4:atest 
4:btest 
4:ctest 
5:atest 
5:btest 
5:ctest 
----ExitLock---- 
----ExitLock---- 
----ExitLock---- 
----Done---- 

私はロックが「無視されている」と仮定しています。なぜなら、私が間違っていれば私を修正するからです。私の質問は:どのように私の期待どおりの動作を得ることができますか?そして、 "非同期操作を使わないでください"というのは受け入れられない答えです。

非同期操作を使用すると、実際には複数の真の並列操作を行うことができない場合がありますが、実際にはより複雑で実用的な使用例がありますが、少なくともキュー操作をキューに入れていても動作をエミュレートする必要があります。それらを次々と実行しています。具体的には、AsyncLineWriterへのインタフェースは変更するべきではありませんが、内部的な動作はスレッドセーフな方法で与えられた非同期操作をキューに入れておく必要があります。私の実際のケースでは、これは私が変更できないメソッドなので、私はLineWriterのWriteLineにロックを追加できないという別の問題があります(この例では実際に期待される出力が得られます)。

同様の問題を解決するために設計されたいくつかのコードへのリンクは、適切な経路で私を得るのに十分かもしれません。あるいは、いくつかの代替案。ありがとう。

P.S.どのようなユースケースがこのようなものを使用するのか疑問に思っているなら、一度に1つの操作しかアクティブにできないネットワーク接続を維持するクラスです。私は各操作のために非同期呼び出しを使用しています。本当に並行した通信回線を単一のネットワーク接続で運用するための明白な方法はないため、相互に待ち合わせる必要があります。

+0

私が「期待した」と言ったところでは、私はおそらく「望んでいる」と言っていたはずです。 –

答えて

4

ここではロックが機能しない可能性があります。あるスレッド(呼び出し側スレッド)にロックを入力し、別のスレッド(ThreadPool)上でそのスレッドを終了しようとしています。
.Netロックはリエントラントなので、呼び出し元スレッドに2度目の入力を待つことはありません。

代わりに、同期バージョンをロック内で呼び出し、そのメソッドを非同期に呼び出すメソッドを作成する必要があります(デッドロックが発生する可能性があります)。
この方法では、呼び出し元のスレッドではなく非同期のスレッドにロックを設定し、メソッドの終了後に同じスレッドで終了させます。


ただし、ロックを待機しているスレッドを無駄にしているため、これは非効率的です。
asyncメソッドを呼び出すと、そのスレッドのコピーが1つ開始されるか、スレッドが既に実行されている場合にキューに追加されるように、デリゲートのキューを持つ単一のスレッドを実行してください。
このスレッドがキューを終了すると、そのスレッドは終了し、次に非同期メソッドを呼び出すと再起動します。 私はこれを同様の文脈で行っています。 my earlier questionを参照してください。

自分でIAsyncResultを実装するか、コールバックパターンを使用する必要があります(これは一般的にははるかに簡単です)。

+0

さて、「同期バージョンにはロックを入れることができないため、そのコードを編集することはできません」と言っていましたが、「新しいコードにロックを入れて、 "?うーん...なぜ私はそれを考えなかったのか分からない。そして私はあなたが私の質問全体をどのようにスピードアップして投稿してから30秒以内にそれを答えたかもわかりません。それはかなり驚くべきことです。ありがとうございました。 –

+0

私は以前も同様のことを扱ってきました。私のアプローチを取っておきたいのなら、[これらのコメント]を読んでください(http://stackoverflow.com/questions/6456571/how-can-i-make-sure-that-exactly-one-thread-will-do-something/ 6456603#6456603)。 – SLaks

+0

私たちのユースケースでは、たとえそれが非効率的であっても、正しい振る舞いを示し、まれにしか起こりません。私はそれがより頻繁に起こっていることがわかったら、後でキューを使用しようとするかもしれません。 –

0

私は、以下の "Asynchronizer" クラスを作成:

public IAsyncResult BeginWriteLine(string line, AsyncCallback callback, object state) 
{ 
    Action action =() => m_LineWriter.WriteLine(line); 
    return m_Asynchronizer.DoAction.BeginInvoke(action, callback, state); 
} 
public void EndWriteLine(IAsyncResult result) 
{ 
    m_Asynchronizer.DoAction.EndInvoke(result); 
} 

それも彼ら場合は、複数の非同期操作を同期するのは簡単です。この方法は:あなたはこのようにそれを使用することができます

public class Asynchronizer 
{ 
    public readonly Action<Action> DoAction; 

    public Asynchronizer() 
    { 
     m_Lock = new object(); 
     DoAction = new Action<Action>(ActionWrapper); 
    } 
    private object m_Lock; 
    private void ActionWrapper(Action action) 
    { 
     lock (m_Lock) 
     { 
      action(); 
     } 
    } 
} 

をメソッドシグネチャが異なっている例えば、我々はまた、これを行うことができます:

public IAsyncResult BeginWriteLine2(string line1, string line2, AsyncCallback callback, object state) 
{ 
    Action action =() => m_LineWriter.WriteLine(line1, line2); 
    return m_Asynchronizer.DoAction.BeginInvoke(action, callback, state); 
} 
public void EndWriteLine2(IAsyncResult result) 
{ 
    m_Asynchronizer.DoAction.EndInvoke(result); 
} 

は、ユーザーは、彼らが望む限り多くのBeginWriteLine/EndWriteLineとBeginWriteLine2は/ EndWriteLine2をキューすることができ、それらは(千鳥、同期、ファッションに呼び出されます、あなたが、操作が保留中のものと同数のスレッドをオープンしますので、一度に非同期操作が保留されることがわかっている場合にのみ実用的です)。より良い、より複雑なソリューションは、SLaksが指摘したように、専用のキュースレッドを使用し、そのキューにアクションをキューイングすることです。

+1

ところで、デリゲートタイプを 'Action 'に置き換えることができます。 – SLaks

+1

実際には、 'Asynchronizer'をもっと複雑なロックフリーなクラスに置き換えることで、残りのコードを変更せずに、自分のソリューションを実際にドロップすることができます。 – SLaks

+0

クール!それは、Asynchronizer(それが純粋であるように)がテストに合格しているので、聞くのは良いことです。次のステップは、あなたの代わりに交換を試みることです。 –

関連する問題