parallel.for
多くのスレッドで外部プログラムを起動するために使用しています。しかし、これらは別々のスレッドであるという事実にもかかわらず、遅延のような sth を実装する必要があります。たとえば、2 つのスレッドがこの外部プログラムを同時に起動したい場合、そのうちの 1 つが 2 番目のスレッドの 10 秒後に待機して開始する必要があります。
出来ますか?
parallel.for
多くのスレッドで外部プログラムを起動するために使用しています。しかし、これらは別々のスレッドであるという事実にもかかわらず、遅延のような sth を実装する必要があります。たとえば、2 つのスレッドがこの外部プログラムを同時に起動したい場合、そのうちの 1 つが 2 番目のスレッドの 10 秒後に待機して開始する必要があります。
出来ますか?
可能ですが、提供した情報を考えると無意味に思えます...外部プログラムのシングルスレッド実行を強制しているため、単一のスレッドで実行することもできます。「外部プログラム」を開始するためThread 2
に待機する必要がある場合は、「外部プログラム」をいつ開始したかを既に認識しているため、すべての作業をそのまま行います。Thread 1
Thread 1
マルチスレッドアプローチから得られる唯一の利点は、「外部プログラム」を実行する前に実行する必要がある処理が多数あり、その処理が同時実行の適切な候補である必要がある場合です。
メイン/GUI スレッドの応答性を維持するために、追加のスレッドを 1 つだけ使用してこれを行う方法がいくつかあります。最初のアプローチは、操作している外部リソースを単純にロックすることです。
public class ExternalResourceHandler
{
private readonly ExternalResource _resource;
private readonly object _sync = new object();
// constructors
// ...
// other methods
public void PerformExternalOperation()
{
lock(_sync)
{
Result result = _resource.Execute();
// do soemthing with the result
}
}
}
コードを実行するための 3 つのマルチスレッド バージョンを次に示します。
Parallel.For
: 外部プログラムの実行に短時間かかる場合に推奨 - 25 秒未満のものをお勧めします (ただし、これは必ずしも「正しい」数値ではありません)。ThreadPool
と、25 秒もかからないことをお勧めします (上記と同じ条件で)。Thread
、操作がより長く実行される場合に推奨されます (つまり、25 秒を超えるが、25 秒未満の場合でも同様に機能します)。以下にいくつかの例を示します (必ずしも機能的ではなく、主にさまざまなアプローチのアイデアを提供することを目的としています)。
public class Program
{
public static ExternalResourceHandler _erh = new ExternalResourceHandler();
static int Main()
{
Console.WriteLine("Type 'exit' to stop; 'parallel', 'pool' or 'thread' for the corresponding execution version.");
string input = Console.ReadLine();
while(input != "exit")
{
switch(input)
{
case "parallel":
// Run the Parallel.For version
ParallelForVersion();
break;
caase "pool":
// Run the threadpool version
ThreadPoolVersion();
break;
case "thread":
// Run the thread version
ThreadVersion();
break;
default:
break;
}
input = Console.ReadLine();
}
return 0;
}
public static void ParallelForVersion()
{
Parallel.For(0, 1, i =>
{
_erh.PerformExternalOperation();
});
}
public static void ThreadPoolVersion()
{
ThreadPool.QueueUserWorkItem(o=>
{
_erh.PerformExternalOperation();
});
}
public static void ThreadVersion()
{
Thread t = new Thread(()=>
{
_erh.PerformExternalOperation();
});
t.IsBackground = true;
t.Start();
}
}
もう 1 つのオプションは、プロデューサー/コンシューマー設計パターンを採用することです。このパターンでExternalResourceHandler
は、ユーザーがコンシューマーになり、スレッドセーフ キューから外部リソースへの要求を処理します。メインスレッドはリクエストをキューに入れるだけで、すぐに作業に戻ります。次に例を示します。
public class ExternalResourceHandler
{
private volatile boolean _running;
private readonly ExternalResource _resource;
private readonly BlockingQueue<Request> _requestQueue;
public ExternalResourceHandler( BlockingQueue<Request> requestQueue)
{
_requestQueue = requestQueue;
_running = false;
}
public void QueueRequest(Request request)
{
_requestQueue.Enqueue(request);
}
public void Run()
{
_running = true;
while(_running)
{
Request request = null;
if(_requestQueue.TryDequeue(ref request) && request!=null)
{
_resource.Execute(request);
}
}
}
// methods to stop the handler (i.e. set the _running flag to false)
}
メインは次のようになります。
public class Program
{
public static ExternalResourceHandler _erh = new ExternalResourceHandler();
static int Main()
{
Thread erhThread = new Thread(()=>{_erh.Run();});
erhThread.IsBackground = true;
erhThread.Start();
Console.WriteLine("Type 'exit' to stop or press enter to enqueue another request.");
string input = Console.ReadLine();
while(input != "exit")
{
_erh.EnqeueRequest(new Request());
input = Console.ReadLine();
}
// Stops the erh by setting the running flag to false
_erh.Stop();
// You may also need to interrupt the thread in order to
// get it out of a blocking state prior to calling Join()
erhThread.Join();
return 0;
}
}
ご覧のとおり、どちらの場合も、外部ハンドラーのすべての作業は単一のスレッドで強制されますが、メイン スレッドは引き続き応答します。
生産者と消費者のパターンを見てください。最初のスレッドは「外部プログラムが起動されました」という情報を生成し、2 番目のスレッドはそれを消費し、10 秒待ってから外部プログラムを起動します。