4

私は現在、ネットワーク経由で収集デバイスを制御する必要があるサーバー アプリケーションに取り組んでいます。このため、多くの並列プログラミングを行う必要があります。時間の経過とともに、処理エンティティ (スレッド/プロセス/アプリケーション) 間の通信には 3 つのアプローチがあることを学びました。残念ながら、3 つのアプローチにはそれぞれ欠点があります。

A)同期リクエスト(同期関数呼び出し)を行うことができます。この場合、呼び出し元は、関数が処理されて応答が受信されるまで待機します。例えば:

const bool convertedSuccessfully = Sync_ConvertMovie(params);

問題は、発信者がアイドリングしていることです。時々、これはオプションではありません。たとえば、呼び出しがユーザー インターフェイス スレッドによって行われた場合、応答が到着するまでアプリケーションがブロックされているように見えますが、これには長い時間がかかる可能性があります。

B)非同期リクエストを作成し、コールバックが行われるのを待つことができます。クライアントコードは、実行する必要があるものは何でも続行できます。

Async_ConvertMovie(params, TheFunctionToCallWhenTheResponseArrives);

このソリューションには、コールバック関数が必然的に別のスレッドで実行されるという大きな欠点があります。問題は、発信者に応答を返すのが難しいことです。たとえば、サービスを非同期的に呼び出したダイアログのボタンをクリックしましたが、コールバックが到着したときにダイアログが長時間閉じられていました。

void TheFunctionToCallWhenTheResponseArrives()
{
    //Difficulty 1: how to get to the dialog instance?
    //Difficulty 2: how to guarantee in a thread-safe manner that
    //              the dialog instance is still valid?
}

これ自体はそれほど大きな問題ではありません。ただし、そのような呼び出しを複数回行いたい場合、それらはすべて前の呼び出しの応答に依存します。私の経験では、これは手に負えないほど複雑になります。

C)最後のオプションは、非同期リクエストを作成し、レスポンスが到着するまでポーリングを続けることです。has-the-response-arrived-yet チェックの合間に、何か役に立つことができます。これは、実行する一連の非同期関数呼び出しがある場合を解決するために、私が知っている最善の解決策です。これは、応答が到着したときに呼び出し元のコンテキスト全体がまだ残っているという大きな利点があるためです。また、呼び出しの論理的な順序はかなり明確なままです。例えば:

const CallHandle c1 = Sync_ConvertMovie(sourceFile, destFile);
while(!c1.ResponseHasArrived())
{
    //... do something in the meanwhile
}
if (!c1.IsSuccessful())
    return;

const CallHandle c2 = Sync_CopyFile(destFile, otherLocation);
while(!c1.ResponseHasArrived())
{
    //... do something in the meanwhile
}
if (c1.IsSuccessful())
    //show a success dialog

この 3 番目の解決策の問題は、呼び出し元の関数から戻ることができないことです。これは、間にやりたい作業が非同期で行われている作業とはまったく関係がない場合には適していません。長い間、上記のオプションの欠点を持たない関数を非同期的に呼び出す可能性があるかどうか疑問に思っていました。誰かがアイデアを持っていますか、おそらく何か巧妙なトリックはありますか?

注:例は C++ ライクな擬似コードです。ただし、この質問は C# と Java、そしておそらく他の多くの言語にも同様に当てはまると思います。

4

3 に答える 3

3

select非同期ネットワーク タスクのループやウィンドウ システムのメッセージ ループなどの従来のアプローチとあまり変わらない、明示的な「イベント ループ」または「メッセージ ループ」を検討できます。到着したイベントは、例 B のように、適切なときにコールバックにディスパッチされる場合がありますが、場合によっては、たとえば有限状態マシンでトランザクションを発生させるために、異なる方法で追跡される場合もあります。FSM は、結局のところ、多くのステップを必要とするプロトコルに沿った対話の複雑さを管理する優れた方法です!

これらの考慮事項を体系化する 1 つのアプローチは、 Reactor設計パターンから始まります。

Schmidt のACEの一連の作業は、C++ のバックグラウンドを持っている場合、これらの問題の出発点として適しています。Twistedも、Python のバックグラウンドからすると非常に価値があります。そして、あなたが言うように、「他の多くの言語」にも同様のフレームワークと一連のホワイトペーパーが存在すると確信しています (私が提供したウィキペディアの URL は、ACE と Twisted 以外の他の言語の Reactor 実装を指しています)。

于 2009-06-25T15:51:41.287 に答える
2

私はBに行きがちですが、呼び出して呼び出すのではなく、フォローアップを含むすべての処理を別のスレッドで行います. その間、メイン スレッドは GUI を更新し、スレッドが完了するのをアクティブに待機する (つまり、進行状況バーを含むダイアログを表示する) か、単にバックグラウンドで処理を実行させ、完了時に通知を受け取ることができます。処理スレッドの観点からは、処理全体が実際に同期しているため、これまでのところ複雑な問題はありません。GUI の観点からは、非同期です。

それに加えて、.NET では GUI スレッドに切り替えても問題ありません。BackgroundWorker クラスと ThreadPool を使用すると、これも簡単になります (記憶が正しければ、ThreadPool を使用しました)。たとえば、Qt では、C++ を使い続けることも非常に簡単です。

前回の主要なアプリケーションでこのアプローチを使用しましたが、非常に満足しています。

于 2009-06-25T15:49:50.083 に答える
1

Alex が言ったように、Doug Schmidt が Patterns of Software Architecture で文書化した Proactor と Reactor を見てください。

ACE には、さまざまなプラットフォーム用のこれらの具体的な実装があります。

于 2009-06-25T15:58:26.373 に答える