Как отвечать на события, когда в .NET выполняется многопоточная операция - PullRequest
2 голосов
/ 30 июня 2011

У меня есть класс для запуска фоновых операций в приложении WinForms.Мне нужно написать этот фоновый рабочий, так как мои реквизиты используют .NET 1.1, поэтому я не могу использовать BackgroundWorker , который доступен только из .NET 2.0

Этот класс получает делегат и выполняет егов потоке.Я хочу, чтобы основной поток отвечал на события.

Я также хочу указать, что операция выполняется, установив курсор приложения на Cursors.WaitCursor.

Что вы думаете о текущей реализации?Мне интересен метод WaitTillThreadFinishes(), потому что я не уверен насчет Application.DoEvents(), пожалуйста, прочитайте код и поделитесь со мной мнениями о WaitTillThreadFinishes.

Следующий код выполняет операция:

private object ExecuteOperation (Delegate target, params object[] parameters)
{
    mTargetDelegate = target;
    mTargetParameters = parameters;

    mTargetThread = new Thread(new ThreadStart(ThreadProc));
    mTargetThread.Name = mTargetDelegate.Method.Name;

    mOperationFinished = false;

    // start threaded operation
    mTargetThread.Start();

    // perform active waiting
    WaitTillThreadFinishes();

    return mTargetResult;
 }

Следующий код выполняется в потоке, просто вызовите делегата и оберните исключения:

protected virtual void ThreadProc()
{
    try
    {
        mTargetResult = mTargetDelegate.DynamicInvoke(mTargetParameters);
    }
    catch (ThreadAbortException) { }
    catch (Exception ex)
    {
        //manage exceptions here ...
    }
    finally
    {
        mOperationFinished = true;
    }
}

И это кодвыполняет активное ожидание .Я заинтересован в том, чтобы поделиться с вами.Есть ли лучший вариант?Любая боль вызывает Application.DoEvents() массово?

private void WaitTillThreadFinishes ()
{
    // Active wait to respond to events with a WaitCursor
    while (!mOperationFinished)
    {
        // sleep to avoid CPU usage
        System.Threading.Thread.Sleep(100);
        Application.DoEvents();
        Cursor.Current = Cursors.WaitCursor;
    }
    Cursor.Current = Cursors.Default;
}

Заранее спасибо.

Ответы [ 4 ]

3 голосов
/ 30 июня 2011

Пожалуйста, дайте мне знать, если я правильно понял ваш вопрос.Почему вы не используете событие, чтобы уведомить пользовательский интерфейс о том, что работник закончил свою работу?Таким образом, пользовательский интерфейс не будет заблокирован работником, и вы избежите занятого ожидания.

Пример реализации

public class MyBackgroundWorker
{
    // Fields
    private Delegate _target;
    private object[] _arguments;

    // Events
    public event EventHandler RunWorkerStarted;
    public event EventHandler<RunWorkerCompletedEventArgs> RunWorkerCompleted;

   // Event Invocators
    public void InvokeRunWorkerStarted()
    {
        var handler = RunWorkerStarted;
        if (handler != null) handler(this, new EventArgs());
    }

    public void InvokeRunWorkerCompleted(object result)
    {
        var handler = RunWorkerCompleted;
        if (handler != null) handler(this, new RunWorkerCompletedEventArgs(result));
    }

    public void RunWorkerAsync(Delegate target, params object[] arguments)
    {
        _target = target;
        _arguments = arguments;

        new Thread(DoWork).Start(arguments);
    }

    // Helper method to run the target delegate
    private void DoWork(object obj)
    {
        _target.DynamicInvoke(_arguments);
        // Retrieve the target delegate's result and invoke the RunWorkerCompleted event with it (for simplicity, I'm sending null)
        InvokeRunWorkerCompleted(null);
    }
}

internal class RunWorkerCompletedEventArgs : EventArgs
{
    public RunWorkerCompletedEventArgs(object result)
    {
        Result = result;
    }

    public object Result { get; set; }
}

Использование

В пользовательском интерфейсе вы можете использовать его следующим образом:

    private void button1_Click(object sender, EventArgs e)
    {
        var worker = new MyBackgroundWorker();
        worker.RunWorkerStarted += worker_RunWorkerStarted;
        worker.RunWorkerCompleted += worker_Completed;
        worker.RunWorkerAsync(new MethodInvoker(SomeLengthyOperation), null);
    }

    void worker_RunWorkerStarted(object sender, EventArgs e)
    {

    }

    void worker_Completed(object sender, EventArgs e)
    {
        MessageBox.Show("Worker completed");
    }

    private void SomeLengthyOperation()
    {
       Thread.Sleep(5000);
    }

Заключительные замечания

Не забудьте вызвать Invoke () в обработчиках событий для доступапоток пользовательского интерфейса правильно.Вы также можете изменить работника, чтобы сделать это безопасным способом.

1 голос
/ 30 июня 2011

В 1.1 не так много поддержки для этого, но я расскажу вам, что я буду делать (извините, сейчас нет кода).

Что касается асинхронной операции, я 'd используйте APM , чтобы запустить и завершить асинхронный метод.Это полностью поддерживается в 1.1, так что не беспокойтесь.

Идея состоит в том, что в пользовательском интерфейсе вы сохраняете некоторое указание о том, что работа выполняется (например, поле логическое ) и (необязательно) Timer, используемое для "пробуждения".«Пользовательский интерфейс на регулярной основе проверяет текущее состояние фоновой работы и сообщает об этом пользователю.

Вы должны установить логический , чтобы указать, что вы работаете в фоновом режиме,вызовите BeginInvoke() вашего делегата (используя перегрузку, при которой принимает обратный вызов , выполняет поиск «Выполнение метода обратного вызова при завершении асинхронного вызова» ) и запускает Timer.Когда пользователь пытается использовать пользовательский интерфейс, вы можете дополнительно проверить boolean и отменить операцию, тем самым не давая пользователю сделать что-то вредное во время ожидания.Когда таймер Tick s, вы можете проверить состояние вашего асинхронного метода, скажем, через общее поле, в которое метод записывает обновления и читает пользовательский интерфейс.Например, double, который пользовательский интерфейс использует для обновления индикатора выполнения.

После срабатывания обратного вызова вы убираете асинхронный беспорядок (т. Е. Вызываете EndInvoke, обрабатываете любые исключения и т.д.)отключите Timer и сбросьте поле boolean running индикации.

Используя этот метод, вы можете сохранить пользовательский интерфейс полностью отзывчивым (и частично используемым, в зависимости от вашего общего дизайна), может настроить механизм прерывания фонового работника (с помощью другого поля, обратного логическому , упомянутому ранее, и информировать пользователя о состоянии операции.

0 голосов
/ 30 июня 2011

Иногда бывает так, что мы можем запустить поток и дождаться его возврата, если вы тем временем занимаетесь чем-то другим, но в этом случае с показанным вами кодом это бессмысленно.

Если вы хотите, чтобы threadProc разрешал обрабатывать события, то вызовите в нем doevents, что на короткое время освободит ЦП, позволяя обрабатывать.

Если у вас нет особых причин для необходимости обрабатывать потоки,ты не должен этого делать.Понимать это правильно, как сказал Ян Бойд, сложно, и чем больше вам нужно с ним взаимодействовать, тем сложнее.Если вы можете запускать потоки "забей и забудь", это будет проще всего.

0 голосов
/ 30 июня 2011

В идеале вы запускаете асинхронную операцию и оставляете свою форму в покое (за исключением, возможно, использования курсора Cursors.AppStarting).

Когда ваша многопоточная операция завершается, она должна запустить какое-то событие BackgroundOperationComplete,Вот где вы будете вызывать из асинхронного кода делегата:

form.Invoke(BackgroundOperationComplete);

В методе BackgroundOperationComplete формы вы можете обработать тот факт, что фоновая операция завершена:

void BackgroundOperationComplete()
{
    this.Cursor = Cursors.DefaultCursor;

    lblAnswer.Text = "The thread is done";
}

Если ничего не помогло, сохраните операцию синхронной и используйте IProgressDialog.(краткий концептуальный псевдокод из памяти):

void DoStuff()
{
   IProgressDialog pd = new ProgressDialog();
   pd.SetTitle = "Calculating Widgets";
   pd.StartTimer(PDTIMER_RESET, NULL)
   pd.StartProgressDialog(this.Handle, NULL,  PROGDLG_MODAL | PROGDLG_NOTIME | PROGDLG_NOPROGRESSBAR | PROGDLG_NOCANCEL, NULL);
   try
   {
      pd.SetLine(1, "Please wait while the widgets are frobbed");

      DoTheThingThatDoesTheSynchronousStuff();
   }
   finally
   {
      pd.StopProgressDialog();
   }
   pd = null;
}
...