1

完了すると、一般的な引数で呼び出されるコールバックを受け取るアクションがありますAction<Action<T>>。アクションの開始時にビジー状態のスピナーを表示し、コールバックの呼び出し時にスピナーを取り除きたいので、それを行うための簡単なユーティリティを作成しました。私が抱えている問題は、ユーザーがコールバックが元の呼び出しスレッドで実行されることを期待しているが、常にそうであるとは限らないことです。ほとんどの場合、単体テスト (nUnit) では完全に機能しますが、アプリケーションが実際に実行されているとき (WPF、.Net 4) の一部の呼び出しでは機能しません。

ここに私が持っているものの関連部分があります

void WrapAsyncCallbackPattern<T>(Action<T> callback, Action<Action<T>> actionToRun)
{
    var subject = new AsyncSubject<T>();
    try
    {
        actionToRun(
            result =>
            {
                subject.OnNext(result);
                subject.OnCompleted();
            });
    }
    catch (Exception ex)
    {
        subject.OnError(ex);
    }

    subject
        .ObserveOn(Scheduler.CurrentThread)
        .Subscribe(callback, OnError);
}

callbackサブスクライブしている (そして宣言されている) スレッドで実行したいのですsubjectが、確実に実行されないようです。私はばかげたことをしていると思います。それは何ですか?

編集:ユニットテストコードを追加

private readonly TimeSpan m_WaitTime = TimeSpan.FromSeconds(1);

[Test]
public void WrapAsyncCallbackPattern_WithActionOnDifferentThread_CallsCallbackOnSameThread()
{
    var awaiter = new AutoResetEvent(false);

    bool callbackRan = false;
    int callingThreadId = Thread.CurrentThread.ManagedThreadId;
    int callbackThreadId = int.MinValue;
    int actionThreadId = int.MinValue;

    BackgroundOperation.WrapAsyncCallbackPattern<int>(
        _ =>
        {
            callbackRan = true;
            callbackThreadId = Thread.CurrentThread.ManagedThreadId;

            awaiter.Set();
        },
        cb => ThreadPool.QueueUserWorkItem(
            _ =>
            {
                actionThreadId = Thread.CurrentThread.ManagedThreadId;
                cb(0);
            }));

    var errorInfo = string.Format("\r\nCalling thread = {0}; Action thread = {1}; Callback thread = {2}", callingThreadId, actionThreadId, callbackThreadId);

    Assert.IsTrue(awaiter.WaitOne(m_WaitTime));
    Assert.IsTrue(callbackRan);
    Assert.AreNotEqual(callingThreadId, actionThreadId, "Action needs to be run on a different thread for this test." + errorInfo);
    Assert.AreNotEqual(actionThreadId, callbackThreadId, "Callback should not be run on action thread." + errorInfo);
    Assert.AreEqual(callingThreadId, callbackThreadId, "Callback should be run on calling thread." + errorInfo);
}
4

1 に答える 1

8

あなたはおそらく何が何をするのかについて間違った理解をしてScheduler.CurrentThreadいます。誰もがこの間違いを犯すと思います。

スケジューラはCurrentThread、オブザーバブルが定義されている (またはサブスクライブされている) ときではなく、実行中のオブザーバブルに関連しています。遅延実行または遅延実行を考えてください。これは、別のスレッドにジャンプするたびに、呼び出しをマーシャリングする何らかの方法が必要になるため、理にかなっています。

だからあなたが本当に求めているのは次のようなものです:

var synchContext = new SynchronizationContextScheduler(
    System.Threading.SynchronizationContext.Current) 

subject
    .ObserveOn(synchContext)
    .Subscribe(callback, OnError);

または多分:

subject
    .ObserveOn(this) /* this is my current form */
    .Subscribe(callback, OnError);

これを行うと、コールバックが実行されるスレッドを制御できるはずです。

テストは同期的に実行されたため、おそらく機能しました。

于 2012-11-16T06:00:09.310 に答える