2

多数のtime\CPU集約型プロセス(TimeExpensiveタイプ)を次々に実行しています。メインスレッド(A)は、別のスレッド(B)でTimeExpensiveプロセスを非同期的に開始し、非アクティブになります。完了時に、スレッドBは呼び出し元の完了ハンドラーを同期的に起動し、スレッドBで次のTimeExpensiveプロセスを開始します。新しいスレッド(C)が作成されますが、Cの開始後、Bは終了します。したがって、n個のプロセスの場合、n個のスレッドが作成され、ほとんどの場合、それらは共存しません。

線形シングルスレッド方式での実装を希望する場合もありますが、TimeExpensiveはサードパーティによって実装されており、実行中はすべてのシステムコアを使用し、数時間実行されます。

//This will run as console app 
class Program
{
    static void Main(string[] args)
    {
        new Program().StartJobs();
    }

    void StartJobs()
    {
        Main mainJob = new Main();
        mainJob.MainCompletionEvent += 
                     new Action<object, EventArgs>(mainJob_MainCompletionEvent);
        mainJob.Start();
    }

    void mainJob_MainCompletionEvent(object sender, EventArgs e)
    {
        //if(success) Environment.Exit(0);
    }

}

class Main
{
    int processCounter = 0;
    public event Action<object, EventArgs> MainCompletionEvent;
    public void Start()
    {
        //...do other important tasks here...
        processCounter++;
        TimeExpensive te = new TimeExpensive();
        te.CompletionEvent += new Action(TimeExpensive_CompletionHandler);
        Thread aThread = new Thread(te.Run);
        aThread.IsBackground = false;
        aThread.Name = "TimeExpensive Thread: " + processCounter;
        aThread.Start();
    }

    void TimeExpensive_CompletionHandler()
    {
        Console.WriteLine("current Thread Name: " + Thread.CurrentThread.Name);
        //Start another Process In Background if
        if (processCounter < 5)
        {
            Start();
        }
        else
        {
            Console.ReadKey();
            if (JobCompletionEvent != null)
                JobCompletionEvent(this, new EventArgs());
        }
    }
}

class TimeExpensive
{
    public event Action CompletionEvent;

    public void Run()
    {
        //doing time expensive task
        //...

        //when finish Notify completion Handler...
        if (CompletionEvent != null)
        {
            CompletionEvent();
        }
    }
}

//Output
current Thread Name: TimeExpensive Thread: 1
current Thread Name: TimeExpensive Thread: 2
current Thread Name: TimeExpensive Thread: 3
current Thread Name: TimeExpensive Thread: 4
current Thread Name: TimeExpensive Thread: 5

上記の実装は、私が説明した動作を模倣しています。私を悩ませているのは、次のスレッドが開始するまで同期的に実行されているイベントハンドラーであり、この間、設計されていない多くのタスクを実行しています。

これが適切かどうかわからない場合、スレッドBの完了ハンドラーにいる間にスレッドAに戻る方法はありますか?または、別のデリゲートを使用してイベントハンドラーの実行を開始する方がよいでしょうか。BeginInvoke?

シンプルでありながら安全なアプローチでやりたいです。どんな助けでも大歓迎です。

PS私はたくさんの投稿を読みましたが、誰もこのシナリオをうまく扱っていませんでした。

編集

コンソールアプリでこのコードをキックスタートする方法を示すために、静的メインが追加されました。「メイン」ジョブを開始するためのUIを作成することもできることを忘れないでください。mainJobオブジェクトを作成して実行するためのBackgroundWorkerスレッドを確実に作成します。ありがとう!

4

2 に答える 2

7

スレッドBの完了ハンドラーにいる間にスレッドAに戻る方法はありますか?

いいえ、あるスレッドから別のスレッドへの呼び出しをマーシャリングするための配管がありません。この種の配管は、GUIアプリのメインスレッドによって提供されます。必然的に、ユーザーインターフェイスは基本的にスレッドセーフではありません。このようなマーシャリングをサポートするUIスレッドの実装の詳細がいくつかあります。これは、プロデューサー/コンシューマースレッドモデルの一般的な実装ではコンシューマーのように機能します。

これには、スレッドセーフなキューと、キューを読み取るコンシューマーのループが必要です。これは、WindowsGUIアプリのメッセージキューとして認識できます。GetMessageを呼び出して通知を読み取り、それに基づいて動作するメッセージループを使用します。呼び出しのマーシャリングが簡単になりました。メッセージをメッセージキューに投稿するだけで、UIスレッドがそれを読み取り、リクエストを実行します。投稿は、Winformsの場合はControl.BeginInvoke、WPFの場合はDispatcher.BeginInvokeによって実装されます。

確かに、この同期メカニズムは自分で実装できます。.NET4BlockingCollectionクラスを使用すると簡単にできます。ただし、スレッドAの実行方法を根本的に変更する必要があることに注意してくださいキューに投稿されたリクエストに応答し続けることが重要です。BackgroundWorkerのようなクラスが解決しようとする種類の問題。GUIメッセージループは必要であるため存在することに注意してください。UIはスレッドセーフではありません。コンソールアプリには(通常)同じ種類の負担はありません。コンソールはスレッドセーフです。

于 2012-05-04T11:30:23.513 に答える
0

あなたが経験している問題は、正しくスレッド化することがどれほど難しいかによるものです。私はあなたの例を俳優でコーディングしました:

type Actor<'a> = MailboxProcessor<'a>

type SupMsg = WaitForDone of AsyncReplyChannel<string>
type ProgramState = RunNumber of int * Actor<WorkerMsg> option
and WorkerMsg = Begin of Id * AsyncReplyChannel<string>
and Id = int

let startComputation () = Actor.Start(fun inbox ->
  async { 
    let! Begin(id, chan) = inbox.Receive()
    printfn "Running Computation"
    do! Async.Sleep(20) // zZz
    chan.Reply(sprintf "'%i is done!'" id) })

let sup () = Actor.Start(fun inbox ->
  let rec loop state =
    async {
      match state with
      | RunNumber(_, None) -> return! loop <| RunNumber(1, Some(startComputation ()))
      | RunNumber(run, Some(active)) ->
        let! completed = active.PostAndAsyncReply(fun chan -> Begin(run, chan))
        printfn "sup observed: %s" completed
        let active' = Some(startComputation ())
        if run <> 5 then return! loop <| RunNumber(run + 1, active')
        else return! isDone () }
  and isDone () =
    async {
      let! WaitForDone(chan) = inbox.Receive()
      return chan.Reply("all done") }

  loop <| RunNumber(0, None))

[<EntryPoint>]
let main args =
  printfn "%s" <| (sup ()).PostAndReply(fun chan -> WaitForDone(chan))
  0

出力として:

> main();;
Running Computation
sup observed: '1 is done!'
Running Computation
sup observed: '2 is done!'
Running Computation
sup observed: '3 is done!'
Running Computation
sup observed: '4 is done!'
Running Computation
sup observed: '5 is done!'
all done
val it : int = 0

ご覧のとおり、スレッド間で通信するのは簡単です。ライブラリがサードパーティのライブラリである場合はAsync.Sleep(20)、ライブラリの呼び出しに簡単に置き換えることができます。

于 2012-05-04T10:14:06.800 に答える