3

私は次のコードを持っています:

[TestMethod]
public void StartWorkInFirstThread()
{
    if (SynchronizationContext.Current == null)
        SynchronizationContext.SetSynchronizationContext(
            new SynchronizationContext());

    var syncContext = SynchronizationContext.Current;

    Console.WriteLine("Start work in the first thread ({0})", 
        Thread.CurrentThread.ManagedThreadId);

    var action = ((Action) DoSomethingInSecondThread);
    action.BeginInvoke(CallbackInSecondThread, syncContext);

    // Continue its own work
}

private static void DoSomethingInSecondThread()
{
    Console.WriteLine("Do something in the second thread ({0})", 
        Thread.CurrentThread.ManagedThreadId);   
}

private void CallbackInSecondThread(IAsyncResult ar)
{
    Console.WriteLine("Callback in the second thread ({0})", 
        Thread.CurrentThread.ManagedThreadId);
    var syncContext = (SynchronizationContext) ar.AsyncState;
    syncContext.Post(CallbackInFirstThread, null);
}

private void CallbackInFirstThread(object obj)
{
    Console.WriteLine("Callback in the first thread ({0})",
        Thread.CurrentThread.ManagedThreadId);
}

Post()このコンテキストのメソッドを呼び出すため、最後のメソッドが最初のスレッド、つまりSynchronizationContextが取得される最初のスレッドで実行されることを期待しています。つまり、次のようなものです。

Start work in the first thread (28)
Do something in the second thread (17)
Callback in the second thread (17)
Callback in the first thread (28)

それはSynchronizationContextの意味ではありませんか?しかし、実際には次の出力があります。

Start work in the first thread (28)
Do something in the second thread (17)
Callback in the second thread (17)
Callback in the first thread (7)

何が問題ですか?彼らはSynchronizationContextに何か問題がありますか、それとも私はいくつかの誤解を持っていますか?

更新:このメソッドを、Resharperテストランナーを使用した単体テストと呼びます。

4

3 に答える 3

8

http://www.codeproject.com/KB/threads/SynchronizationContext.aspxを参照してください

あなたが必要とする答えがあります。SynchronizationContext操作を適切に処理するには、オーバーライドする必要があります。

から始めて読む:

DoWorkは、Run1と同じスレッド11で実行されることに注意してください。メインスレッドへのSynchronizationContextはあまりありません。なんで?どうしたの?ええと...これは、人生で無料のものは何もないことに気付いたときの部分です。スレッドは、スレッド間でコンテキストを切り替えるだけでなく、そのためにはインフラストラクチャが組み込まれている必要があります。たとえば、UIスレッドはメッセージポンプを使用し、SynchronizationContext内で、メッセージポンプを利用してUIスレッドに同期します。

于 2010-10-08T17:05:49.230 に答える
5

SynchronizationContextのデフォルトの実装は、呼び出し元のスレッド(コンテキストをキャプチャするスレッドではなく、Send / Postメソッドを呼び出すスレッド)で渡されたデリゲートを実行するだけです。一部の操作に対するスレッドアフィニティなど、特定の動作が必要な場合は、これを手動で実装する必要があります。BCLには、WindowsFormsSynchronizationContextDispatcherSynchronizationContextなど、UIの相互運用性を簡素化するためのすぐに使用できる実装がいくつか含まれています。

于 2010-10-08T17:01:32.390 に答える
3

実行中のスレッドにデリゲートを「注入」する一般的な方法がないため、期待は間違っています。「最初のスレッド」はテストランナーで開始され、1つ以上のテストを実行してから停止します。これを中断して、実行するように指示する方法はありませんCallbackInFirstThreadSynchronizationContextクラスPostはスレッドプールで-edデリゲートを実行します。これは、クラスが持つ唯一のオプションであるためです。

のような派生クラスWindowsFormsSynchronizationContextは、WinFormsアプリケーションのメッセージループを利用してPost-edデリゲートをUIスレッドに渡しますが、テストランナーには同等のものはありません。

テストしているコードが使用していることを確認したい場合SynchronizationContextは、テストで確認できるフラグを設定する独自の派生クラスを作成できます。次に例を示します。

public class TestSynchronizationContext : SynchronizationContext
{
    [ThreadStatic]
    private static object _CurrentPostToken;
    /// <summary>
    /// Gets the context's token, if the current thread is executing a delegate that
    /// was posted to this context; otherwise, null.
    /// </summary>
    public static object CurrentPostToken
    {
        get
        {
            return _CurrentPostToken;
        }
    }

    public object Token { get; private set; }

    /// <summary>
    /// Gets a WaitHandle that is set after the context executes a posted delegate.
    /// </summary>
    public AutoResetEvent PostHandle { get; private set; }

    public TestSynchronizationContext(object token)
    {
        Token = token;
        PostHandle = new AutoResetEvent(false);
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        try
        {
            _CurrentPostToken = Token;
            // Execute the callback on this thread, so that we can reset the context
            // when it's finished.
            d(state);
        }
        finally
        {
            _CurrentPostToken = null;
        }

        // The test method will wait on this handle so that it doesn't exit before
        // the synchronization context is called.
        PostHandle.Set();
    }
}

で、コンテキストを:StartWorkInFirstThreadのインスタンスに設定します。TestSynchronizationContext

SynchronizationContext.SetSynchronizationContext(
        new TestSynchronizationContext(new object()));

を呼び出した後、テストを終了する前にが発生するBeginInvokeのを待つ必要があるため、次のように呼び出します。Post

((TestSynchronizationContext)SynchronizationContext.Current).PostHandle.WaitOne(1000);

ではCallbackInFirstThread、次のようなものでどのコンテキストが使用されているかを確認できます。

Assert.IsNotNull(TestSynchronizationContext.CurrentPostToken);

重要なのは、実際に最初のスレッドにポストバックする簡単な方法はないということですが、コードが実際のアプリケーションで実行されるときにコールバックがUIスレッドで実行されるように、適切なコンテキストが使用されていることを確認できます。

于 2013-01-03T17:32:43.303 に答える