C#パラレルプログラミングの反発ロックの使用


基本的な使用
パラレルプログラミングでは、臨界領域へのアクセスはよく発生する問題であり、ロックを追加し、ロックを解放することはよく使用される解決策である.例:
lock(_OneObject)
{
    Do something with _OneObject ...
}

ロックは、System.Threading.Monitor.Enterメソッドを呼び出し、System.Threading.Monitor.Exitによって解放します.上のコードは
bool lockToken = false;
try
{
    Monitor.Enter(_OneObject, ref lockToken);
    Do something with _OneObject ...
}
finally
{
    if (lockToken)
        Monitor.Exit(_OneObject);
}

比較するとlockコードの使用は簡潔で、Monito.Exit()の解放の問題を心配する必要はありませんが、Monitorはより豊富なロック制御インタフェースを提供しています.実際の状況に応じて選択して使用することができます.
コードの例:
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sample5_2_monitor_lock
{
    class Program
    {
        private static int _TaskNum = 3;
        private static Task[] _Tasks;
        private static StringBuilder _StrBlder;
        private const int RUN_LOOP = 50;

        private static void Work1(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;
            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0} Task : #{1} Value: {2} =====
"
, DateTime.Now.TimeOfDay, TaskID, i); i++; try { lockToken = false; Monitor.Enter(_StrBlder, ref lockToken); _StrBlder.Append(log); } finally { if (lockToken) Monitor.Exit(_StrBlder); } } } private static void Work2(int TaskID) { int i = 0; string log = ""; bool lockToken = false; while (i < RUN_LOOP) { log = String.Format("Time: {0} Task : #{1} Value: {2} *****
"
, DateTime.Now.TimeOfDay, TaskID, i); i++; try { lockToken = false; Monitor.Enter(_StrBlder, ref lockToken); _StrBlder.Append(log); } finally { if (lockToken) Monitor.Exit(_StrBlder); } } } private static void Work3(int TaskID) { int i = 0; string log = ""; bool lockToken = false; while (i < RUN_LOOP) { log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~
"
, DateTime.Now.TimeOfDay, TaskID, i); i++; try { lockToken = false; Monitor.Enter(_StrBlder, ref lockToken); _StrBlder.Append(log); } finally { if (lockToken) Monitor.Exit(_StrBlder); } } } static void Main(string[] args) { _Tasks = new Task[_TaskNum]; _StrBlder = new StringBuilder(); _Tasks[0] = Task.Factory.StartNew((num) => { var taskid = (int)num; Work1(taskid); }, 0); _Tasks[1] = Task.Factory.StartNew((num) => { var taskid = (int)num; Work2(taskid); }, 1); _Tasks[2] = Task.Factory.StartNew((num) => { var taskid = (int)num; Work3(taskid); }, 2); var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => { Task.WaitAll(_Tasks); Console.WriteLine("=========================================================="); Console.WriteLine("All Phase is completed"); Console.WriteLine("=========================================================="); Console.WriteLine(_StrBlder); }); try { finalTask.Wait(); } catch (AggregateException aex) { Console.WriteLine("Task failed And Canceled" + aex.ToString()); } finally { } Console.ReadLine(); } } }

テストの結果はもちろん私たちの予想通りで、とても完璧で、ここでは無視しました.
しかし、ロックされたテスト結果がなければ、スレッドごとに50回しかループしていないのに、文字化けしていることがわかります.
ロックタイムアウトの使用
主にMonitor.TryEnter()を使用し、タイムアウト時間を設定するパラメータが1つ増えています.コードでは、各ロックのタイムアウトTimerを2秒、ワーク1で5秒として実行し、ワーク2とワーク3のタイムアウトをもたらします.
コードの例:
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Sample5_3_monitor_lock_timeout
{
    class Program
    {
        private static int _TaskNum = 3;
        private static Task[] _Tasks;
        private static StringBuilder _StrBlder;
        private const int RUN_LOOP = 50;

        private static void Work1(int TaskID)
        {
            int i = 0;
            string log = "";
            bool lockToken = false;
            while (i < RUN_LOOP)
            {
                log = String.Format("Time: {0} Task : #{1} Value: {2} =====
"
, DateTime.Now.TimeOfDay, TaskID, i); i++; try { lockToken = false; Monitor.TryEnter(_StrBlder, 2000, ref lockToken); if (!lockToken) { Console.WriteLine("Work1 TIMEOUT!! Will throw Exception"); throw new TimeoutException("Work1 TIMEOUT!!"); } System.Threading.Thread.Sleep(5000); _StrBlder.Append(log); } finally { if (lockToken) Monitor.Exit(_StrBlder); } } } private static void Work2(int TaskID) { int i = 0; string log = ""; bool lockToken = false; while (i < RUN_LOOP) { log = String.Format("Time: {0} Task : #{1} Value: {2} *****
"
, DateTime.Now.TimeOfDay, TaskID, i); i++; try { lockToken = false; Monitor.TryEnter(_StrBlder, 2000, ref lockToken); if (!lockToken) { Console.WriteLine("Work2 TIMEOUT!! Will throw Exception"); throw new TimeoutException("Work2 TIMEOUT!!"); } _StrBlder.Append(log); } finally { if (lockToken) Monitor.Exit(_StrBlder); } } } private static void Work3(int TaskID) { int i = 0; string log = ""; bool lockToken = false; while (i < RUN_LOOP) { log = String.Format("Time: {0} Task : #{1} Value: {2} ~~~~~
"
, DateTime.Now.TimeOfDay, TaskID, i); i++; try { lockToken = false; Monitor.TryEnter(_StrBlder, 2000, ref lockToken); if (!lockToken) { Console.WriteLine("Work3 TIMEOUT!! Will throw Exception"); throw new TimeoutException("Work3 TIMEOUT!!"); } _StrBlder.Append(log); } finally { if (lockToken) Monitor.Exit(_StrBlder); } } } static void Main(string[] args) { _Tasks = new Task[_TaskNum]; _StrBlder = new StringBuilder(); _Tasks[0] = Task.Factory.StartNew((num) => { var taskid = (int)num; Work1(taskid); }, 0); _Tasks[1] = Task.Factory.StartNew((num) => { var taskid = (int)num; Work2(taskid); }, 1); _Tasks[2] = Task.Factory.StartNew((num) => { var taskid = (int)num; Work3(taskid); }, 2); var finalTask = Task.Factory.ContinueWhenAll(_Tasks, (tasks) => { Task.WaitAll(_Tasks); Console.WriteLine("=========================================================="); Console.WriteLine("All Phase is completed"); Console.WriteLine("=========================================================="); Console.WriteLine(_StrBlder); }); try { finalTask.Wait(); } catch (AggregateException aex) { Console.WriteLine("Task failed And Canceled" + aex.ToString()); } finally { } Console.ReadLine(); } } }