C#で接続されたFixedThreadPoolのテストタスク。 ここで何が間違っていますか? UPD

更新:間違ったコードを修正する喜びを否定できませんでした。 「エラーの処理」セクションを追加しました。このセクションでは、受け取ったコメントに基づいて修正されたコードと修正の説明を提供します。



これは、単なる有用な情報ではなく、専門家への質問後の質問です。 ディスカッションに招待します。

最近、幸運にもSvyaznoyの履歴書を.NET開発者の地位に送ることができました。 応答として、マルチスレッドの知識テストを行うように頼まれました。 自分をこの分野の専門家と呼ぶことはできませんが、それでも、次の要件をどのように実装するかが完全に理解できました。



JavaのFixedThreadPoolに似たC#クラスの実装が必要です。次の要件があります。





タスクは、使用するプリミティブ、最も単純なスレッドですべてを実行するか、ThreadPool、TPLなどを使用するかを指定しなかったため、最も基本的な要素であるThread、ManualResetEventsなどを使用することを決定しました。 数時間で書いて送った。 今日、私は人事担当者に電話をかけ、回答を受け取りました。これは次のようなものでした:「必要なものにさえ近づいていません」。 これは私を困惑させました。コードが機能し、テストされているため、明らかな欠陥はありません。



したがって、あなたの判断で、FixedThreadPoolと関連クラスの実装を紹介します。 私はすぐに、彼らの意見では、実装に誤りがあると警告します。したがって、私の考えを基礎として取る価値はありません。 コードに関するコメント:







FixedThreadPool
/// <summary> ///  ,           /// . /// </summary> /// <remarks> ///     . /// </remarks> public sealed class FixedThreadPool { #region Constructors /// <summary> ///         ///  . /// </summary> /// <param name="aConcurrentTaskNumber"> ///     . /// </param> /// <param name="aLog"> ///  . /// </param> /// <param name="aPriorityLog"> ///        . /// </param> /// <exception cref="ArgumentOutOfRangeException"> ///       . /// </exception> public FixedThreadPool (int aConcurrentTaskNumber, ILog aLog = null, ILog aPriorityLog = null) { if (aConcurrentTaskNumber <= 1) { throw new ArgumentOutOfRangeException( "aConcurrentTaskNumber", "       ."); } Log = aLog; PriorityLog = aPriorityLog; mConcurrentTaskNumber = aConcurrentTaskNumber; LogMessage("  ."); Thread lTaskSchedulerThread = new Thread(TaskSchedulerLogic) {Name = " ."}; lTaskSchedulerThread.Start(); LogMessage("  ."); } #endregion #region Public methods /// <summary> ///   <paramref name="aTask"/>       /// <paramref name="aTaskPriority"/>. /// </summary> /// <param name="aTask"> ///       .. /// </param> /// <param name="aTaskPriority"> ///  . /// </param> /// <returns> /// <see langword="true"/> -      . /// <see langword="false"/> -        ,   ///     . /// </returns> /// <exception cref="ArgumentNullException"> ///         . /// </exception> public bool Execute(Task aTask, TaskPriority aTaskPriority) { if (aTask == null) { throw new ArgumentNullException( "aTask", "        ."); } LogMessage("    ."); lock (mIsStoppedLock) { if (IsStopped) { //  . LogPriority(aTaskPriority, ConsoleColor.DarkGray); //   . return false; } } //    . EnqueueTask(aTask, aTaskPriority); LogMessage("    .", ConsoleColor.DarkYellow); return true; } /// <summary> ///       ,   .  ///         . /// </summary> /// <remarks> ///             ///    <see cref="Execute"/>   <see langword="false"/>. /// ,     ,     . /// </remarks> public void Stop() { //     . lock (mIsStoppedLock) { IsStopped = true; LogMessage("  ."); } //     ,   . LogMessage( "        .", ConsoleColor.DarkRed); lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.Pulse(mTaskSchedulerLock); } mPoolStoppedGate.WaitOne(); LogMessage("      .", ConsoleColor.DarkRed); } #endregion #region Private properties /// <summary> /// /  ,    . /// </summary> /// <value> /// <see langword="true"/> -   ,      ///  . <see langword="false"/> -   . /// </value> /// <remarks> ///    <see cref="mIsStoppedLock"/>. /// </remarks> private bool IsStopped { get; set; } /// <summary> /// /    . /// </summary> private ILog PriorityLog { get; set; } /// <summary> /// /   . /// </summary> private ILog Log { get; set; } #endregion #region Private methods /// <summary> ///     .    . /// </summary> private void TaskSchedulerLogic() { lock (mTaskSchedulerLock) { while (true) { //     ,   mTaskSchedulerLock. Monitor.Wait(mTaskSchedulerLock); lock (mQueuedTasksLock) { lock (mIsStoppedLock) { lock (mRunningTasksLock) { if (IsStopped && !mRunningTasks.Any() && !mQueuedTasks.Any()) { LogMessage( "          ."); LogMessage(" -    ."); //      . mPoolStoppedGate.Set(); return; } } } } lock (mQueuedTasksLock) { if (!mQueuedTasks.Any()) { //   . //  . continue; } } lock (mRunningTasksLock) { if (mRunningTasks.Count >= mConcurrentTaskNumber) { //    . //  . continue; } } LogMessage( "     .", ConsoleColor.DarkRed); //       . TaskListEntry lTask = DequeueTask(); LogMessage(" -     ."); //      . lTask.Task.Finished += OnTaskFinished; //      . lock (mRunningTasksLock) { mRunningTasks.Add(lTask); } if (lTask.TaskPriority == TaskPriority.High) { //     . //          . Interlocked.Increment(ref mQueuedHighPriorityTaskCounter); } else if (lTask.TaskPriority == TaskPriority.Normal) { //     . //          HighPriorityTaskFactor. Interlocked.Add( ref mQueuedHighPriorityTaskCounter, -HighPriorityTaskFactor); } //    . lTask.Task.Execute(); LogMessage( string.Format( " -     {0}.", lTask.TaskPriority), ConsoleColor.DarkYellow); lock (mRunningTasksLock) { LogMessage( string.Format( "    {0} .", mRunningTasks.Count)); } } } } /// <summary> ///    . /// </summary> /// <param name="aSender"> /// - . /// </param> /// <param name="aEventArgs"> ///  . /// </param> private void OnTaskFinished(object aSender, EventArgs aEventArgs) { Task lSender = aSender as Task; Debug.Assert( lSender != null, "  aSender      ."); //    ,       . lSender.Finished -= OnTaskFinished; //     . lock (mRunningTasksLock) { //         . TaskListEntry lEntry = mRunningTasks.First(aEntry => aEntry.Task == lSender); mRunningTasks.Remove(lEntry); LogMessage( string.Format("   {0} .", lEntry.TaskPriority), ConsoleColor.Red); } lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.Pulse(mTaskSchedulerLock); } } /// <summary> ///   <paramref name="aTask"/>     . /// </summary> /// <param name="aTask"> /// ,      . /// </param> /// <param name="aTaskPriority"> ///  . /// </param> private void EnqueueTask(Task aTask, TaskPriority aTaskPriority) { TaskListEntry lEntry = new TaskListEntry(aTask, aTaskPriority); LogPriority(aTaskPriority, ConsoleColor.Green); lock (mQueuedTasksLock) { //       . mQueuedTasks.Add(lEntry); LogMessage( string.Format( "      {0}", lEntry.TaskPriority), ConsoleColor.Green); //      . LogMessage("     .", ConsoleColor.DarkRed); } lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.Pulse(mTaskSchedulerLock); } } /// <summary> ///         .    ///   ,   . /// </summary> /// <returns> /// ,      . /// </returns> private TaskListEntry DequeueTask() { TaskListEntry lNextTask; lock (mQueuedTasksLock) { lNextTask = FindNextTaskUsingPriorityRules(); LogPriority(lNextTask.TaskPriority, ConsoleColor.Red); LogMessage( string.Format( "       {0}.", lNextTask.TaskPriority), ConsoleColor.DarkRed); mQueuedTasks.Remove(lNextTask); } lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.Pulse(mTaskSchedulerLock); } return lNextTask; } /// <summary> ///       . /// </summary> /// <returns> ///     . /// </returns> /// <remarks> ///    :       ///     .  ,     ,   ///        .   ,   /// ,    ,      .   ///   ,   ,      -, ///      -  . /// </remarks> private TaskListEntry FindNextTaskUsingPriorityRules() { TaskListEntry lNextTask; lock (mQueuedTasksLock) { Debug.Assert( mQueuedTasks.Count > 0, " FindNextTaskUsingPriorityRules   ,    ."); //        . TaskPriority lNextTaskPriority = TaskPriority.High; //        . if (mQueuedTasks.All(aEntry => aEntry.TaskPriority == TaskPriority.Low)) { //        . //      . lNextTaskPriority = TaskPriority.Low; } else { //        . if (mQueuedTasks.Any( aEntry => aEntry.TaskPriority == TaskPriority.Normal) && (mQueuedTasks.All( aEntry => aEntry.TaskPriority != TaskPriority.High) || Interlocked.CompareExchange(ref mQueuedHighPriorityTaskCounter, 0, 0) >= HighPriorityTaskFactor)) { //            //         //         . //      . lNextTaskPriority = TaskPriority.Normal; } } lNextTask = mQueuedTasks.First( aEntry => aEntry.TaskPriority == lNextTaskPriority); } return lNextTask; } /// <summary> ///    . /// </summary> /// <param name="aMessage"> /// . /// </param> /// <param name="aColor"> ///  . /// </param> private void LogMessage(string aMessage, ConsoleColor aColor = ConsoleColor.Yellow) { if (Log == null) { return; } Log.WriteMessage(aMessage, aColor); } /// <summary> ///     . /// </summary> /// <param name="aTaskPriority"> ///  . /// </param> /// <param name="aColor"> ///  . /// </param> private void LogPriority(TaskPriority aTaskPriority, ConsoleColor aColor) { if (PriorityLog == null) { return; } string lPriority = aTaskPriority == TaskPriority.High ? "H" : aTaskPriority == TaskPriority.Normal ? "N" : "L"; PriorityLog.WriteMessage(lPriority, aColor); } #endregion #region Private data /// <summary> ///     ,        /// ,        ,     ///    . /// </summary> private const int HighPriorityTaskFactor = 3; /// <summary> ///     .    ///   . /// </summary> private readonly int mConcurrentTaskNumber; /// <summary> ///      <see cref="IsStopped"/>. /// </summary> private readonly object mIsStoppedLock = new object(); /// <summary> ///    . ,       ///   . /// </summary> private readonly ManualResetEvent mPoolStoppedGate = new ManualResetEvent(false); /// <summary> ///  ,     . /// </summary> /// <remarks> ///    <see cref="mQueuedTasksLock"/>. /// </remarks> private readonly IList<TaskListEntry> mQueuedTasks = new List<TaskListEntry>(); /// <summary> ///       <see cref="mQueuedTasks"/>. /// </summary> private readonly object mQueuedTasksLock = new object(); /// <summary> ///      . /// </summary> /// <remarks> ///    <see cref="mRunningTasksLock"/>. /// </remarks> private readonly IList<TaskListEntry> mRunningTasks = new List<TaskListEntry>(); /// <summary> ///     <see cref="mRunningTasks"/>. /// </summary> private readonly object mRunningTasksLock = new object(); /// <summary> ///  ,   /  . /// </summary> private readonly object mTaskSchedulerLock = new object(); /// <summary> ///     ,   .    ///        ,     ///       <see cref="HighPriorityTaskFactor"/>. /// </summary> /// <remarks> ///          /// <see cref="Interlocked"/>. /// </remarks> private int mQueuedHighPriorityTaskCounter; #endregion #region Nested type: TaskListEntry /// <summary> ///   . /// </summary> /// <remarks> ///     . /// </remarks> private struct TaskListEntry { #region Constructors /// <summary> ///          . /// </summary> /// <param name="aTask"> /// . /// </param> /// <param name="aTaskPriority"> ///  . /// </param> public TaskListEntry(Task aTask, TaskPriority aTaskPriority) { mTask = aTask; mTaskPriority = aTaskPriority; } #endregion #region Public properties /// <summary> /// . /// </summary> public Task Task { get { return mTask; } } /// <summary> ///  . /// </summary> public TaskPriority TaskPriority { get { return mTaskPriority; } } #endregion #region Private data private readonly Task mTask; private readonly TaskPriority mTaskPriority; #endregion } #endregion }
      
      







タスク
  /// <summary> ///     <see cref="FixedThreadPool1"/>. /// </summary> public class Task { #region Constructors /// <summary> ///        <see cref="FixedThreadPool1"/> ///   . /// </summary> /// <param name="aTaskBody"> ///   . /// </param> /// <exception cref="ArgumentNullException"> ///     . /// </exception> public Task(Action aTaskBody) { if (aTaskBody == null) { throw new ArgumentNullException("aTaskBody", "    ."); } TaskBody = aTaskBody; } #endregion #region Public properties /// <summary> /// /   . /// </summary> public Action TaskBody { get; private set; } #endregion #region Events /// <summary> /// ,     . /// </summary> public event EventHandler Finished; #endregion #region Public methods /// <summary> ///   . /// </summary> public void Execute() { Thread lTaskThread = new Thread( () => { //  . TaskBody(); //    . EventHandler lFinished = Finished; if (lFinished != null) { lFinished(this, EventArgs.Empty); } }) {Name = "Task thread."}; lTaskThread.Start(); } #endregion }
      
      







タスクの優先度
 /// <summary> ///  . /// </summary> public enum TaskPriority { /// <summary> ///  . /// </summary> High = 0, /// <summary> ///  . /// </summary> Normal, /// <summary> ///  . /// </summary> Low }
      
      









エラー処理



あなたのコメントと建設的な批判に感謝します。 修正されたソリューションを公開しない場合、トピックを閉じないことにしました。 最初に、推奨事項をリストし、コードに含まれる推奨事項と含まれない推奨事項、およびその理由を説明します。



修正されたテストプロジェクトはここからダウンロードできます。 修正されたコード:



FixedThreadPool
 // : Eshva.Threading //  : FixedThreadPool.cs // GUID : 7F1EECB7-F28A-4A20-9536-26D174BCD437 // : Mike Eshva (mike@eshva.ru) //  : 04.06.2012 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; namespace Eshva.Threading.Framework { /// <summary> ///  ,           /// . /// </summary> /// <remarks> ///     . /// </remarks> public sealed class FixedThreadPool { #region Constructors /// <summary> ///         ///  . /// </summary> /// <param name="aConcurrentTaskNumber"> ///     . /// </param> /// <param name="aLog"> ///  . /// </param> /// <param name="aPriorityLog"> ///        . /// </param> /// <exception cref="ArgumentOutOfRangeException"> ///       . /// </exception> public FixedThreadPool (int aConcurrentTaskNumber, ILog aLog = null, ILog aPriorityLog = null) { if (aConcurrentTaskNumber <= 1) { throw new ArgumentOutOfRangeException( "aConcurrentTaskNumber", "       ."); } Log = aLog; PriorityLog = aPriorityLog; for (int lThreadIndex = 0; lThreadIndex < aConcurrentTaskNumber; lThreadIndex++) { string lThreadName = string.Format("Task thread #{0}", lThreadIndex); Thread lTaskThread = new Thread(TaskThreadLogic) {Name = lThreadName}; lTaskThread.Start(); } } #endregion #region Public methods /// <summary> ///   <paramref name="aTask"/>       /// <paramref name="aTaskPriority"/>. /// </summary> /// <param name="aTask"> ///       .. /// </param> /// <param name="aTaskPriority"> ///  . /// </param> /// <returns> /// <see langword="true"/> -      . /// <see langword="false"/> -        ,   ///     . /// </returns> /// <exception cref="ArgumentNullException"> ///         . /// </exception> public bool Execute(Task aTask, TaskPriority aTaskPriority) { if (aTask == null) { throw new ArgumentNullException( "aTask", "        ."); } LogMessage("    ."); lock (mIsStoppedLock) { if (IsStopped) { //  . LogPriority(aTaskPriority, ConsoleColor.DarkGray); //   . return false; } } //    . EnqueueTask(aTask, aTaskPriority); LogMessage("    .", ConsoleColor.DarkYellow); return true; } /// <summary> ///       ,   .  ///         . /// </summary> /// <remarks> ///             ///    <see cref="Execute"/>   <see langword="false"/>. /// ,     ,     . /// </remarks> public void Stop() { //     . lock (mIsStoppedLock) { IsStopped = true; LogMessage("  ."); } //     ,   . LogMessage( "        .", ConsoleColor.DarkRed); lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.PulseAll(mTaskSchedulerLock); } mPoolStoppedGate.WaitOne(); LogMessage("      .", ConsoleColor.DarkRed); } #endregion #region Private properties /// <summary> /// /  ,    . /// </summary> /// <value> /// <see langword="true"/> -   ,      ///  . <see langword="false"/> -   . /// </value> /// <remarks> ///    <see cref="mIsStoppedLock"/>. /// </remarks> private bool IsStopped { get; set; } /// <summary> /// /    . /// </summary> private ILog PriorityLog { get; set; } /// <summary> /// /   . /// </summary> private ILog Log { get; set; } #endregion #region Private methods private void TaskThreadLogic() { lock (mTaskSchedulerLock) { while (true) { //     ,   mTaskSchedulerLock. Monitor.Wait(mTaskSchedulerLock); lock (mQueuedTasksLock) { if (!mQueuedTasks.Any()) { lock (mIsStoppedLock) { if (IsStopped) { LogMessage( "          ."); LogMessage(" -    ."); //      . mPoolStoppedGate.Set(); return; } } //    ,       . //    . continue; } } LogMessage( "     .", ConsoleColor.DarkRed); //       . TaskListEntry lTask = DequeueTask(); LogMessage(" -     ."); switch (lTask.TaskPriority) { case TaskPriority.High: Interlocked.Increment(ref mQueuedHighPriorityTaskCounter); break; case TaskPriority.Normal: Interlocked.Add( ref mQueuedHighPriorityTaskCounter, -HighPriorityTaskFactor); break; } //    . lTask.Task.Execute(); LogMessage( string.Format( " -     {0}.", lTask.TaskPriority), ConsoleColor.DarkYellow); lock (mQueuedTasksLock) { lock (mIsStoppedLock) { if (IsStopped && !mQueuedTasks.Any()) { LogMessage( "          ."); LogMessage(" -    ."); //      . mPoolStoppedGate.Set(); return; } } } } } } /// <summary> ///   <paramref name="aTask"/>     . /// </summary> /// <param name="aTask"> /// ,      . /// </param> /// <param name="aTaskPriority"> ///  . /// </param> private void EnqueueTask(Task aTask, TaskPriority aTaskPriority) { TaskListEntry lEntry = new TaskListEntry(aTask, aTaskPriority); LogPriority(aTaskPriority, ConsoleColor.Green); lock (mQueuedTasksLock) { //       . mQueuedTasks.Add(lEntry); LogMessage( string.Format( "      {0}", lEntry.TaskPriority), ConsoleColor.Green); //      . LogMessage("     .", ConsoleColor.DarkRed); } lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.Pulse(mTaskSchedulerLock); } } /// <summary> ///         .    ///   ,   . /// </summary> /// <returns> /// ,      . /// </returns> private TaskListEntry DequeueTask() { TaskListEntry lNextTask; lock (mQueuedTasksLock) { lNextTask = FindNextTaskUsingPriorityRules(); LogPriority(lNextTask.TaskPriority, ConsoleColor.Red); LogMessage( string.Format( "       {0}.", lNextTask.TaskPriority), ConsoleColor.DarkRed); mQueuedTasks.Remove(lNextTask); } lock (mTaskSchedulerLock) { //        mTaskSchedulerLock. Monitor.Pulse(mTaskSchedulerLock); } return lNextTask; } /// <summary> ///       . /// </summary> /// <returns> ///     . /// </returns> /// <remarks> ///    :       ///     .  ,     ,   ///        .   ,   /// ,    ,      .   ///   ,   ,      -, ///      -  . /// </remarks> private TaskListEntry FindNextTaskUsingPriorityRules() { TaskListEntry lNextTask; lock (mQueuedTasksLock) { Debug.Assert( mQueuedTasks.Count > 0, " FindNextTaskUsingPriorityRules   ,    ."); //        . TaskPriority lNextTaskPriority = TaskPriority.High; //        . if (mQueuedTasks.All(aEntry => aEntry.TaskPriority == TaskPriority.Low)) { //        . //      . lNextTaskPriority = TaskPriority.Low; } else { //        . if (mQueuedTasks.Any( aEntry => aEntry.TaskPriority == TaskPriority.Normal) && (mQueuedTasks.All( aEntry => aEntry.TaskPriority != TaskPriority.High) || Interlocked.CompareExchange(ref mQueuedHighPriorityTaskCounter, 0, 0) >= HighPriorityTaskFactor)) { //            //         //         . //      . lNextTaskPriority = TaskPriority.Normal; } } lNextTask = mQueuedTasks.First( aEntry => aEntry.TaskPriority == lNextTaskPriority); } return lNextTask; } /// <summary> ///    . /// </summary> /// <param name="aMessage"> /// . /// </param> /// <param name="aColor"> ///  . /// </param> private void LogMessage(string aMessage, ConsoleColor aColor = ConsoleColor.Yellow) { if (Log == null) { return; } Log.WriteMessage(aMessage, aColor); } /// <summary> ///     . /// </summary> /// <param name="aTaskPriority"> ///  . /// </param> /// <param name="aColor"> ///  . /// </param> private void LogPriority(TaskPriority aTaskPriority, ConsoleColor aColor) { if (PriorityLog == null) { return; } string lPriority = aTaskPriority == TaskPriority.High ? "H" : aTaskPriority == TaskPriority.Normal ? "N" : "L"; PriorityLog.WriteMessage(lPriority, aColor); } #endregion #region Private data /// <summary> ///     ,        /// ,        ,     ///    . /// </summary> private const int HighPriorityTaskFactor = 3; /// <summary> ///      <see cref="IsStopped"/>. /// </summary> private readonly object mIsStoppedLock = new object(); /// <summary> ///    . ,       ///   . /// </summary> private readonly ManualResetEvent mPoolStoppedGate = new ManualResetEvent(false); /// <summary> ///  ,     . /// </summary> /// <remarks> ///    <see cref="mQueuedTasksLock"/>. /// </remarks> private readonly IList<TaskListEntry> mQueuedTasks = new List<TaskListEntry>(); /// <summary> ///       <see cref="mQueuedTasks"/>. /// </summary> private readonly object mQueuedTasksLock = new object(); /// <summary> ///  ,   /  . /// </summary> private readonly object mTaskSchedulerLock = new object(); /// <summary> ///     ,   .    ///        ,     ///       <see cref="HighPriorityTaskFactor"/>. /// </summary> /// <remarks> ///          /// <see cref="Interlocked"/>. /// </remarks> private int mQueuedHighPriorityTaskCounter; #endregion #region Nested type: TaskListEntry /// <summary> ///   . /// </summary> /// <remarks> ///     . /// </remarks> private struct TaskListEntry { #region Constructors /// <summary> ///          . /// </summary> /// <param name="aTask"> /// . /// </param> /// <param name="aTaskPriority"> ///  . /// </param> public TaskListEntry(Task aTask, TaskPriority aTaskPriority) { mTask = aTask; mTaskPriority = aTaskPriority; } #endregion #region Public properties /// <summary> /// . /// </summary> public Task Task { get { return mTask; } } /// <summary> ///  . /// </summary> public TaskPriority TaskPriority { get { return mTaskPriority; } } #endregion #region Private data private readonly Task mTask; private readonly TaskPriority mTaskPriority; #endregion } #endregion } }
      
      







タスク
 // : Eshva.Threading //  : Task.cs // GUID : 292467E7-4816-4407-BB9B-3309D13C8614 // : Mike Eshva (mike@eshva.ru) //  : 04.06.2012 using System; namespace Eshva.Threading.Framework { /// <summary> ///     <see cref="FixedThreadPool"/>. /// </summary> public class Task { #region Constructors /// <summary> ///        <see cref="FixedThreadPool"/> ///   . /// </summary> /// <param name="aTaskBody"> ///   . /// </param> /// <exception cref="ArgumentNullException"> ///     . /// </exception> public Task(Action aTaskBody) { if (aTaskBody == null) { throw new ArgumentNullException("aTaskBody", "    ."); } TaskBody = aTaskBody; } #endregion #region Public methods /// <summary> ///   . /// </summary> public void Execute() { TaskBody(); } #endregion #region Private properties /// <summary> /// /   . /// </summary> private Action TaskBody { get; set; } #endregion } }
      
      








All Articles