Пример многопоточной разработки C#

Я относительно новичок в C# /. Net. Я разрабатываю настольное приложение, требующее многопоточности. В качестве основы я придумал следующий образец. Мне было интересно, может ли кто-нибудь указать, как сделать его лучше с точки зрения кодирования, обеспечения безопасности потоков и эффективности.

Надеюсь, в этом есть смысл.


public abstract class ThreadManagerBase
{
    // static class variables

    private static ThreadManagerBase instance = null;
    private static BackgroundWorker thread = null;
    private static ProgressBarUIForm progress = null;

    /// <summary>
    /// Create a new instance of this class. The internals are left to the derived class to figure out.
    /// Only one instance of this can run at any time. There should only be the main thread and this thread.
    /// </summary>
    public abstract static ThreadManagerBase NewInstance();

    /// <summary>
    /// Clears the instance.
    /// </summary>
    public static void ClearInstance()
    {
        instance = null;
    }

    /// <summary>
    /// Initializes the background worker with some presets.
    /// Displays progress bar.
    /// </summary>
    private abstract static void InitializeThread()
    {
        thread = new BackgroundWorker();

        thread.WorkerReportsProgress = true;
        thread.WorkerSupportsCancellation = true;

        thread.DoWork += new DoWorkEventHandler(thread_DoWork);
        thread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(thread_RunWorkerCompleted);
        thread.ProgressChanged += new ProgressChangedEventHandler(thread_ProgressChanged);

        thread.RunWorkerAsync();

        progress = new ProgressBarUIForm();

        progress.EnableCancelButton = true;

        progress.UserCanceled += new EventHandlerCancelClicked(progress_UserCanceled);

        progress.ShowDialog();

        thread.Dispose();

        thread = null;
    }

    private static void progress_UserCanceled(bool userCanceled)
    {
        thread.CancelAsync();
    }

    private static void thread_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progress.SetProgressLevel = e.ProgressPercentage;
        progress.SetProgressMessage = e.UserState.ToString();
    }

    private static void thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        progress.Close();

        progress = null;
    }

    private static void thread_DoWork(object sender, DoWorkEventArgs e)
    {
        ProcessWork();
    }

    private abstract static void ProcessWork()
    {
        // do actuall stuff here.
        // the derived classes will take care of the plumbing.
    }
}

Ответов (6)

Решение

Вам не нужен BackgroundWorker, если вы не хотите, чтобы вас кормили с ложечки, обычные потоки вполне приемлемы, если вы следуете правилам.

Я не вижу веских причин для создания этой абстракции поверх BackgroundWorker. Если вы настаиваете, просто предупреждение: я не уверен, изменилось ли оно в более поздних выпусках, но в NET 2.0 было невозможно отменить обработчик DoWork (если только он не проверял время от времени, если его просили остановить ). Прочтите здесь, чтобы найти решение.

Вы изучали параллельные расширения Microsoft для .NET Framework 3.5 ? Это довольно хорошая библиотека, которая требует много работы от многопоточности.

В MSDN также есть много статей о шаблонах потоков, которые вам тоже стоит изучить. Распределение потоков может стать очень сложным очень быстро. Приятно иметь кого-то еще, кто знает обо всех важных вещах, которые могут пойти не так, и упростить это до библиотеки или шаблона. Конечно, в этом тоже есть опасность, если вы не понимаете подводных камней какого-либо конкретного решения. Итак, убедитесь, что вы хорошо изучили любое решение, которое выберете.

Ниже приведен пример тестового класса для опубликованного мною примера:


   class TestOperation : Operation
   {
      AutoResetEvent mMsgRec;

      public TestOperation(params object[] workerParameters)
         : base(workerParameters)
      {
         CanCancel = true;
         mMsgRec = new AutoResetEvent(false);
         //mSomeEvent += DoSomething();
      }

      protected override void Cancel()
      {
         mMsgRec.Set();
      }

      protected override void Clean()
      {
         //mSomeEvent -= DoSomething();
      }

      protected override void Run(object sender, DoWorkEventArgs e)
      {
         BackgroundWorker bg = (BackgroundWorker)sender;
         for (int i = 0; !bg.CancellationPending && (i < 90); i++)
         {
            bg.ReportProgress(i);
            Thread.Sleep(100);
         }

         for (int i = 90; !bg.CancellationPending && (i < 100); i++)
         {
            mMsgRec.WaitOne(2000, false);
            bg.ReportProgress(i);
         }

         if (bg.CancellationPending)
         {
            e.Cancel = true;
         }
         else
         {
            e.Result = "Complete"; // Or desired result
         }
      }
   }

А вот как будет выглядеть основная форма (очень простой пример):


   public partial class Form1 : Form
   {
      TestOperation t;

      public Form1()
      {
         InitializeComponent();
      }

      private void button1_Click(object sender, EventArgs e)
      {
         t = new TestOperation();
         t.CanCancel = true;

         t.OperationProgressChanged += new OperationProgressChangedEventHandler(t_OperationProgressChanged);
         t.OperationCompleted += new OperationCompletedEventHandler(t_OperationCompleted);

         t.StartOperation();
      }

      void t_OperationCompleted(RunWorkerCompletedEventArgs e)
      {
         progressBar1.Value = 0;
      }

      void t_OperationProgressChanged(ProgressChangedEventArgs e)
      {
         progressBar1.Value = e.ProgressPercentage;
      }

      private void button2_Click(object sender, EventArgs e)
      {
         t.StopOperation();
      }
   }

Я сделал нечто подобное. Есть веская причина, если у вас есть несколько задач, которые вы хотите выполнить, но вы не хотите, чтобы код BackgroundWorker реплицировался через весь проект. У меня индикатор выполнения не привязан к фактическому базовому классу, он есть только в основной форме. Вот решение, которое я придумал:

Ниже приводится базовый класс:


   public abstract class Operation
   {
      #region public Event Handlers

      /// 
      /// The event that updates the progress of the operation
      /// 
      public event OperationProgressChangedEventHandler OperationProgressChanged;

      /// 
      /// The event that notifies that the operation is complete (and results)
      /// 
      public event OperationCompletedEventHandler OperationCompleted;

      #endregion

      #region Members

      // Whether or not we can cancel the operation
      private bool mWorkerSupportsCancellation = false;
      // The task worker that handles running the operation
      private BackgroundWorker mOperationWorker;
      // The operation parameters
      private object[] mOperationParameters;

      #endregion

      /// 
      /// Base class for all operations
      /// 
      public Operation(params object[] workerParameters)
      {
         mOperationParameters = workerParameters;
         // Setup the worker
         SetupOperationWorker();
      }

      #region Setup Functions

      /// 
      /// Setup the background worker to run our Operations
      /// 
      private void SetupOperationWorker()
      {
         mOperationWorker = new BackgroundWorker();
         mOperationWorker.WorkerSupportsCancellation = mWorkerSupportsCancellation;
         mOperationWorker.WorkerReportsProgress = true;
         mOperationWorker.WorkerSupportsCancellation = true;
         mOperationWorker.DoWork += new DoWorkEventHandler(OperationWorkerDoWork);
         mOperationWorker.ProgressChanged += new ProgressChangedEventHandler(OperationWorkerProgressChanged);
         mOperationWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OperationWorkerRunWorkerCompleted);
      }

      #endregion

      #region Properties

      /// 
      /// Whether or not to allow the user to cancel the operation
      /// 
      public bool CanCancel
      {
         set
         {
            mWorkerSupportsCancellation = value;
         }
      }

      #endregion

      #region Operation Start/Stop Details

      /// 
      /// Start the operation with the given parameters
      /// 
      /// The parameters for the worker
      public void StartOperation()
      {
         // Run the worker
         mOperationWorker.RunWorkerAsync(mOperationParameters);
      }

      /// 
      /// Stop the operation
      /// 
      public void StopOperation()
      {
         // Signal the cancel first, then call cancel to stop the test
         if (IsRunning())
         {
            // Sets the backgroundworker CancelPending to true, so we can break
            // in the sub classes operation
            mOperationWorker.CancelAsync();
            // This allows us to trigger an event or "Set" if WaitOne'ing
            Cancel();
            // Wait for it to actually stop before returning
            while (IsRunning())
            {
               Application.DoEvents();
            }
         }
      }

      /// 
      /// Whether or not the operation is currently running
      /// 
      /// 
      public bool IsRunning()
      {
         return mOperationWorker.IsBusy;
      }

      #endregion

      #region BackgroundWorker Events

      /// 
      /// Fires when the operation has completed
      /// 
      /// 
      /// 
      private void OperationWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
         // Allow the sub class to clean up anything that might need to be updated
         Clean();

         // Notify whoever is register that the operation is complete
         if (OperationCompleted != null)
         {
            OperationCompleted(e);
         }
      }

      ///  
      /// Fires when the progress needs to be updated for a given test (we might not care)
      /// 
      /// 
      /// 
      private void OperationWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
      {
         // Notify whoever is register of what the current percentage is
         if (OperationProgressChanged != null)
         {
            OperationProgressChanged(e);
         }
      }

      /// 
      /// Fires when we start the operation (this does the work)
      /// 
      /// 
      /// 
      private void OperationWorkerDoWork(object sender, DoWorkEventArgs e)
      {
         // Run the operation
         Run(sender, e);
      }

      #endregion

      #region Abstract methods

      /// 
      /// Abstract, implemented in the sub class to do the work
      /// 
      /// 
      /// 
      protected abstract void Run(object sender, DoWorkEventArgs e);

      /// 
      /// Called at the end of the test to clean up anything (ex: Disconnected events, etc)
      /// 
      protected abstract void Clean();

      /// 
      /// If we are waiting on something in the operation, this will allow us to
      /// stop waiting (ex: WaitOne).
      /// 
      protected abstract void Cancel();

      #endregion
   }

В настоящее время я исследую Threadmare по адресу http://sklobovsky.nstemp.com/community/threadmare/threadmare.htm для проекта C#. Выглядит очень и очень полезно. Это в Delphi, но принципы применимы к любому языку, который может обрабатывать события.