10

Tasking という名前のコンポーネント (私は変更できません) があるとします。このコンポーネントは、「DoTask」メソッドを公開します。このメソッドは、時間のかかる計算を実行し、イベント TaskCompleted を介して結果を返します。通常、これはユーザーが結果を取得した後に閉じる Windows フォームで呼び出されます。

私の特定のシナリオでは、一部のデータ (データベース レコード) を TaskCompleted で返されたデータに関連付け、それを使用してデータベース レコードを更新する必要があります。

イベントが処理されたときに通知するために AutoResetEvent の使用を調査しました。問題は、AutoResetEvent.WaitOne() がブロックされ、イベント ハンドラーが呼び出されないことです。通常、AutoResetEvents は別のスレッドで呼び出されるため、イベント ハンドラーが呼び出すメソッドと同じスレッド上にあることを意味すると思います。

基本的に、結果がイベントを介して返される非同期呼び出しを、イベントが処理され、結果が両方のイベント ハンドラーにアクセス可能な場所に配置されるまでブロックすることにより、同期呼び出し (つまり、別のクラスから DoSyncTask を呼び出す) に変えたいと考えています。および非同期呼び出しを開始したメソッドを呼び出したメソッド。

public class SyncTask
{
    TaskCompletedEventArgs data;
    AutoResetEvent taskDone;

public SyncTask()
{
    taskDone = new AutoResetEvent(false);
}

public string DoSyncTask(int latitude, int longitude)
{
    Task t = new Task();
    t.Completed = new TaskCompletedEventHandler(TaskCompleted);
    t.DoTask(latitude, longitude);
    taskDone.WaitOne(); // but something more like Application.DoEvents(); in WinForms.
    taskDone.Reset();
    return data.Street;
}

private void TaskCompleted(object sender, TaskCompletedEventArgs e)
{
    data = e;
    taskDone.Set(); //or some other mechanism to signal to DoSyncTask that the work is complete.
}
}

In a Windows App the following works correctly.

public class SyncTask
{
    TaskCompletedEventArgs data;

public SyncTask()
{
    taskDone = new AutoResetEvent(false);
}

public string DoSyncTask(int latitude, int longitude)
{
    Task t = new Task();
    t.Completed = new TaskCompletedEventHandler(TaskCompleted);
    t.DoTask(latitude, longitude);
    while (data == null) Application.DoEvents();

    return data.Street;
}

private void TaskCompleted(object sender, TaskCompletedEventArgs e)
{
    data = e;
}
}

Application.Run が呼び出されず、ApplicationContext オブジェクトが使用できないウィンドウ サービスで、その動作を複製する必要があるだけです。

4

7 に答える 7

3

最近、スレッドで非同期呼び出しとイベントを作成し、それらをメインスレッドに戻すことに問題がありました。

私はSynchronizationContextを使用して物事を追跡しました。以下の(擬似)コードは、現在私のために機能しているものを示しています。

SynchronizationContext context;

void start()
{
    //First store the current context
    //to call back to it later
    context = SynchronizationContext.Current; 

    //Start a thread and make it call
    //the async method, for example: 
    Proxy.BeginCodeLookup(aVariable, 
                    new AsyncCallback(LookupResult), 
                    AsyncState);
    //Now continue with what you were doing 
    //and let the lookup finish
}

void LookupResult(IAsyncResult result)
{
    //when the async function is finished
    //this method is called. It's on
    //the same thread as the the caller,
    //BeginCodeLookup in this case.
    result.AsyncWaitHandle.WaitOne();
    var LookupResult= Proxy.EndCodeLookup(result);
    //The SynchronizationContext.Send method
    //performs a callback to the thread of the 
    //context, in this case the main thread
    context.Send(new SendOrPostCallback(OnLookupCompleted),
                 result.AsyncState);                         
}

void OnLookupCompleted(object state)
{
    //now this code will be executed on the 
    //main thread.
}

これで問題が解決したので、これがお役に立てば幸いです。

于 2009-02-04T10:13:44.350 に答える
2

おそらく、DoSyncTask を取得して、適切な間隔でデータ変数の値をチェックするタイマー オブジェクトを開始することができます。データに値が設定されたら、別のイベントを発生させて、データに値が設定されたことを通知できます (もちろん、タイマーをオフにします)。

かなり醜いハックですが、うまくいく可能性があります... 理論的には。

すみません、半分寝て思いつくのはこれが精一杯です。寝る時間...

于 2009-01-28T05:00:51.603 に答える
2

少なくともすべての .NET クラスを使用して、非同期から同期の問題の解決策を考え出しました。

http://geekswithblogs.net/rgray/archive/2009/01/29/turning-an-asynchronous-call-into-a-synchronous-call.aspx

COM ではまだ動作しません。STAスレッドが原因だと思います。COM OCX をホストする .NET コンポーネントによって発生したイベントは、ワーカー スレッドによって処理されないため、WaitOne() でデッドロックが発生します。

他の誰かが解決策を高く評価するかもしれません:)

于 2009-01-29T21:54:27.590 に答える
0

あなたはほとんどそれを持っています。別のスレッドで実行するにはDoTaskメソッドが必要です。これにより、WaitOne呼び出しによって作業の実行が妨げられることはありません。このようなもの:

Action<int, int> doTaskAction = t.DoTask;
doTaskAction.BeginInvoke(latitude, longitude, cb => doTaskAction.EndInvoke(cb), null);
taskDone.WaitOne();
于 2009-01-28T05:22:20.233 に答える
0

あなたのコードはほぼ正しいです...私はちょうど変更しました

t.DoTask(latitude, longitude);

為に

new Thread(() => t.DoTask(latitude, longitude)).Start();

TaskCompletedと同じスレッドで実行されますDoTask。これはうまくいくはずです。

于 2013-01-09T20:26:38.330 に答える
0

Task が WinForms コンポーネントである場合、スレッド化の問題を非常に認識している可能性があり、メイン スレッドでイベント ハンドラーを呼び出します。

したがって、メッセージ ポンプの発生などに依存している可能性があります。Application.Run には、非 GUI アプリ用のオーバーロードがあります。スレッドを起動してポンプすることで問題が解決するかどうかを確認することを検討してください。

また、Reflector を使用して、コンポーネントのソース コードを調べて、コンポーネントが何をしているのかを把握することもお勧めします。

于 2009-01-28T05:01:46.487 に答える
0

Scott W の回答に対する私のコメントは、読み直した後、少し不可解に思えます。だから私はより明確にさせてください:

while( !done )
{
    taskDone.WaitOne( 200 );
    Application.DoEvents();
}

WaitOne( 200 ) により、1 秒あたり 5 回、制御が UI スレッドに返されます (これは必要に応じて調整できます)。DoEvents() 呼び出しは、Windows イベント キュー (ペイントなどのすべての Windows イベント処理を処理するもの) をフラッシュします。クラスに 2 つのメンバーを追加します (この例では 1 つの bool フラグ「done」と、この例では 1 つの戻りデータ「street」)。

これが、やりたいことを実現する最も簡単な方法です。(私は自分のアプリに非常によく似たコードを持っているので、それが機能することを知っています)

于 2009-02-03T00:04:29.493 に答える