9

私は C# の初心者として、スレッド化に非常に慣れていません。Windows アプリ内で複数のスレッドを起動するプログラムがあります。私の目的は、リスト内のすべての項目に対して新しいスレッドを開始することです。このリストの項目は、ネットワーク上のワークステーション名です。作成された各スレッドは、各マシンで修復を行うように見えます。スレッドが終了すると、見つかったエラーなどのログ ファイルに書き込まれます。では、100 台のマシンと 100 のスレッドがある場合、すべてがいつ終了したかをどのように判断すればよいでしょうか?

以下に私の方法を示します:-

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
    if (machineList.Count() != 0)
    {
        foreach (string ws in machineList)
        {
            new Thread(new ParameterizedThreadStart(fixClient), stack).Start(ws);
        }
    }
    else
    {
         MessageBox.Show("Please import data before attempting this procedure");
    }
}
4

8 に答える 8

12

これを行う方法は、すべてのスレッドへの参照を保持してから、それらに参加することです。これは基本的に、結合されたスレッドが完了するまで現在のスレッドがブロックされることを意味します。

ループを次のように変更します。

foreach (string ws in machineList)
{
   var thread = new Thread(new ParameterizedThreadStart(fixClient), stack);
   _machineThreads.Add(thread)
   thread.Start();
}

(_machineThreads は のリストですSystem.Thread)

次に、次のような方法ですべてが完了するまでブロックできます。

private void WaitUntilAllThreadsComplete()
{
   foreach (Thread machineThread in _machineThreads)
   {
      machineThread.Join();
   } 
}

ただし、ほぼ確実に、説明したシナリオではこれを実行したくありません。

  • 多数のスレッドを作成するべきではありません - 何百ものスレッドを明示的に作成することは良い考えではありません
  • Parallel.ForEachSystem.Threading.Taskを見てみてください。最近では、.Net はスレッド化や非同期タスクを扱う際に多くの助けを提供してくれます。明示的なスレッドを使って「独自に作成」しようとするよりも、.Net をよく読んでおくことを強くお勧めします。
  • これはクリック ハンドラのように見えます。ASP.NET Web フォームですか、それともデスクトップ アプリケーションですか? 前者の場合、リクエストからバックグラウンド タスクを実行するために多数のスレッドを生成することはお勧めしません。どちらの場合でも、スレッドが完了するのを待っている間、Web ページまたは GUI をブロックしたいですか?
于 2012-04-24T08:42:18.833 に答える
4

使用できます: IsAlive。しかし、あなたは次のような参照を保持しています

 Thread t = new Thread(new ThreadStart(...));
 t.start();
 if(t.IsAlive)
 {
    //do something
 }
 else
 {
    //do something else
 }
于 2012-04-24T08:48:31.610 に答える
2

GUI イベント ハンドラーでスレッドの完了などを待機しないでください。多くのスレッドを生成する場合 (はい、そうしないでください。Rob の投稿を参照してください)、またはスレッドプールに多くのタスクを送信する場合、最後に実行を完了したエンティティは、ジョブが完了したことを GUI スレッドに通知する必要があります。通常、これには、残りのタスク/スレッドをカウントダウンし、最後のタスク/スレッドが開始されたときに通知するオブジェクトを呼び出すことが含まれます。System.Threading.CountdownEvent を見てください。

于 2012-04-24T09:35:02.573 に答える
2

Brian のソリューションは完全ではなく、構文エラーが発生します。構文エラーがなければ、それは機能し、最初のポスターの問題を解決します。構文エラーを修正する方法がわからないため、最初の質問を解決できるように、この質問を投稿して解決します。このメッセージを削除しないでください。これは、回答されている最初の質問に関連しています。

@Brian Gideon: 次のコードを除いて、あなたのソリューションは完璧です:

// Define a continuation that happens after everything is done.
parent.ContinueWith(
  () =>
  {
    // Code here will execute after the parent task has finished.
    // You can safely update UI controls here.
  }, TaskScheduler.FromCurrentSynchronizationContext);

これに関する具体的な問題は () => の部分にあります。これにより、Delegate System.Action "System.Threading.Tasks.Task" does not take 0 argumentsという構文エラーが生成されます。

これがうまくいくことを本当に願っていますが、これに対する解決策はわかりません。エラーを調べてみましたが、必要なパラメーターがわかりません。誰かがこれに答えることができれば、それは非常に役に立ちます。これが、この問題の唯一の欠落部分です。

于 2012-08-02T23:08:57.043 に答える
2

これを行うには、CountdownEvent クラスを使用する別の方法があります。

スレッドを開始するコードは、カウンターをインクリメントし、CountdownEvent オブジェクトを各スレッドに渡す必要があります。各スレッドは、終了時に CountdownEvent.Signal() を呼び出します。

次のコードは、このアプローチを示しています。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            int numTasks = 20;
            var rng = new Random();

            using (var finishedSignal = new CountdownEvent(1))
            {
                for (int i = 0; i < numTasks; ++i)
                {
                    finishedSignal.AddCount();
                    Task.Factory.StartNew(() => task(rng.Next(2000, 5000), finishedSignal));
                }

                // We started with a count of 1 to prevent a race condition.
                // Now we must decrement that count away by calling .Signal().

                finishedSignal.Signal(); 
                Console.WriteLine("Waiting for all tasks to complete...");
                finishedSignal.Wait();
            }

            Console.WriteLine("Finished waiting for all tasks to complete.");
        }

        static void task(int sleepTime, CountdownEvent finishedSignal)
        {
            Console.WriteLine("Task sleeping for " + sleepTime);
            Thread.Sleep(sleepTime);
            finishedSignal.Signal();
        }
    }
}
于 2012-04-24T10:16:51.707 に答える
2

最初に何かを片付けましょう。

  • このために個別のスレッドを作成しないでください。スレッドは高価なリソースです。代わりに、スレッド プーリング手法を使用してください。
  • Thread.JoinWaitHandle.WaitOne、またはその他のブロック メカニズムを呼び出して UI スレッドをブロックしないでください。

TPLでこれを行う方法は次のとおりです。

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
  if (machineList.Count() != 0) 
  { 
    // Start the parent task.
    var task = Task.Factory.StartNew(
      () =>
      {
        foreach (string ws in machineList)
        {
          string capture = ws;
          // Start a new child task and attach it to the parent.
          Task.Factory.StartNew(
            () =>
            {
              fixClient(capture);
            }, TaskCreationOptions.AttachedToParent);
        }
      }, TaskCreationOptions.LongRunning);

    // Define a continuation that happens after everything is done.
    task.ContinueWith(
      (parent) =>
      {
        // Code here will execute after the parent task including its children have finished.
        // You can safely update UI controls here.
      }, TaskScheduler.FromCurrentSynchronizationContext);
  } 
  else 
  { 
    MessageBox.Show("Please import data before attempting this procedure"); 
  } 
} 

ここで行っているのは、それ自体が子タスクを起動する親タスクを作成することです。TaskCreationOptions.AttachedToParent子タスクを親タスクに関連付けるために使用していることに注意してください。ContinueWith次に、親とそのすべての子が完了した後に実行される親タスクを呼び出します。TaskScheduler.FromCurrentSynchronizationContextUIスレッドで継続を発生させるために使用します。

を使用した代替ソリューションを次に示しParallel.ForEachます。少しクリーンなソリューションであることに注意してください。

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
  if (machineList.Count() != 0) 
  { 
    // Start the parent task.
    var task = Task.Factory.StartNew(
      () =>
      {
        Parallel.Foreach(machineList,
          ws =>
          {
            fixClient(ws);
          });
      }, TaskCreationOptions.LongRunning);

    // Define a continuation that happens after everything is done.
    task.ContinueWith(
      (parent) =>
      {
        // Code here will execute after the parent task has finished.
        // You can safely update UI controls here.
      }, TaskScheduler.FromCurrentSynchronizationContext);
  } 
  else 
  { 
    MessageBox.Show("Please import data before attempting this procedure"); 
  } 
} 
于 2012-04-24T13:28:36.470 に答える
2

もう 1 つのアイデアは、別のスレッドで Parallel.ForEach を使用することです。これは、修正するマシンが多すぎる場合にも安全です。

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{

    if (machineList.Count() != 0)
    {
        AllFinished=False;
        new Thread(new ThreadStart(fixAllClients).Start();
    }
    else
    {
         MessageBox.Show("Please import data before attempting this procedure");
    }
}

private void fixAllClients(){
    var options = new ParallelOptions{MaxDegreeOfParallelism=10};
    Parallel.ForEach(machineList. options, fixClient);
    AllFinished=True;
}
于 2012-04-24T09:04:29.773 に答える
1

WaitHandle待機しているスレッドごとに作成できます。

WaitHandle[] waitHandles = new WaitHandle[machineList.Count()];

リストに追加ManualResetEventしてスレッドに渡します。

for (int i = 0; i < machineList.Count(); i++)
{
    waitHandles[i] = new ManualResetEvent(false);
    object[] parameters = new object[] { machineList[i], waitHandles[i] };
    new Thread(new ParameterizedThreadStart(fixClient), stack).Start(parameters);
}

// wait until each thread will set its manual reset event to signaled state
EventWaitHandle.WaitAll(waitHandles);

あなたのスレッドメソッドの中で:

public void fixClient(object state)
{
    object[] parameters = (object[])state;
    string ws = (string)parameters[0];
    EventWaitHandle waitHandle = (EventWaitHandle)parameters[1];

    // do your job 

    waitHandle.Set();
}

すべてのスレッドが待機ハンドルを設定すると、メイン スレッドは実行を継続します。

于 2012-04-24T08:55:29.950 に答える