Threading - Как завершить рабочий / фоновый поток с взаимодействием пользовательского интерфейса - PullRequest
2 голосов
/ 15 июня 2011

Compact Framework, Windows Mobile 6, C #.

Я собираюсь попробовать некоторые фоновые потоки на компактной платформе и у меня возник вопрос: прекратить рабочий поток.

Код

У меня есть следующий класс ThreadWorker (код из здесь ), который при выполнении будет выполнять проверку в определенных точках, чтобы определить, должен ли он выйти или нет .....

public class ThreadWorker 
{

    public event EventHandler<ProgressEventArgs> OnProgress;

    protected virtual void Progress(ProgressEventArgs args)
    {
        if (OnProgress != null)
            OnProgress(this, args);
    }

    private void DoLongProcess()
    {
        // This will take a long time.
        Thread.Sleep(15000);
        Progress(new ProgressEventArgs("Some info for the UI to display."));
        Thread.Sleep(15000);
    }

    public void DoSomeBackgroundWork()
    {
        try
        {
            while (!Stopping)
            {
                DoLongProcess();
                if (Stopping) return;

                DoLongProcess();
                if (Stopping) return;

                DoLongProcess();
                if (Stopping) return;

                DoLongProcess();
                if (Stopping) return;
            }
        }
        finally
        {
            SetStopped();
        }

        Console.WriteLine("DoSomeBackgroundWork: Terminating gracefully.");
    }

    /// <summary>
    /// Lock covering stopping and stopped
    /// </summary>
    readonly object locker = new object();

    /// <summary>
    /// Whether or not the worker thread has been asked to stop
    /// </summary>
    bool stopping = false;

    /// <summary>
    /// Whether or not the worker thread has stopped
    /// </summary>
    bool stopped = false;

    /// <summary>
    /// Returns whether the worker thread has been asked to stop.
    /// This continues to return true even after the thread has stopped.
    /// </summary>
    public bool Stopping
    {
        get
        {
            lock (locker)
            {
                return stopping;
            }
        }
    }

    /// <summary>
    /// Returns whether the worker thread has stopped.
    /// </summary>
    public bool Stopped
    {
        get
        {
            lock (locker)
            {
                return stopped;
            }
        }
    }

    /// <summary>
    /// Tells the worker thread to stop, typically after completing its 
    /// current work item. (The thread is *not* guaranteed to have stopped
    /// by the time this method returns.)
    /// </summary>
    public void Stop()
    {
        lock (locker)
        {
            stopping = true;
        }
    }

    /// <summary>
    /// Called by the worker thread to indicate when it has stopped.
    /// </summary>
    void SetStopped()
    {
        lock (locker)
        {
            stopped = true;
        }
    }
}

... и следующая форма инициирует поток ...

public partial class Test : Form
{
    public Test()
    {
        InitializeComponent();
    }

    private ThreadWorker myThreadWorker;
    private Thread t = null;

    private void Test_Load(object sender, EventArgs e)
    {
        myThreadWorker = new ThreadWorker();

        myThreadWorker.OnProgress += new EventHandler<ProgressEventArgs>(myThreadWorker_OnProgress);
    }

    private void miStart_Click(object sender, EventArgs e)
    {
        try
        {
            listResults.Items.Insert(0, "Set-up Thread.");
            t = new Thread(myThreadWorker.DoSomeBackgroundWork);
            t.Name = "My Thread";
            t.Priority = ThreadPriority.BelowNormal;
            t.Start();

            listResults.Items.Insert(0, "Thread started.");

        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private void miStop_Click(object sender, EventArgs e)
    {
        try
        {
            listResults.Items.Insert(0, "Waiting for My Thread to terminate.");
            listResults.Refresh();
            myThreadWorker.Stop();
            t.Join();
            listResults.Items.Insert(0, "My Thread Finished.");
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    private delegate void InsertToListBoxDelegate(String text);
    private void InsertToListBox(String text)
    {
        if (InvokeRequired)
            Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text });
        else
        {
            listResults.Items.Insert(0, "{0}".With(text));
            listResults.Refresh();
        }
    }

    void myThreadWorker_OnProgress(object sender, ProgressEventArgs e)
    {
        InsertToListBox(e.Text);
    }
}

Проблема

Когда я нажимаю кнопку Стоп, она вызывает ...

myThreadWorker.Stop();
t.Join();
listResults.Items.Insert(0, "My Thread Finished.");

... я ожидал, что ThreadWorker продолжит работу с текущим DoLongProcess () до его завершения и будет по-прежнему вызывать событие в пользовательском интерфейсе через обработчик событий OnProgress и myThreadWorker_OnProgress.

Однако на самом деле происходит, когда OnProgress вызывается, приложение останавливается при чтении строки ...

 Invoke(new InsertToListBoxDelegate(InsertToListBox), new object[] { text });

Вопрос

Как мне позвонить ...

myThreadWorker.Stop();
t.Join();

... и по-прежнему отвечать на события из фонового потока, пока он не завершится?

Ответы [ 3 ]

7 голосов
/ 15 июня 2011

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

Самая большая проблема - вызов Join.Было бы лучше избегать вызова Join из потока пользовательского интерфейса.Вместо этого отключите кнопку остановки после ее нажатия, чтобы предоставить пользователю обратную связь о том, что запрос на остановку был принят и ожидает рассмотрения.Возможно, вы даже захотите отобразить простое сообщение в строке состояния с указанием как можно более понятного.Затем, когда возникнет последнее событие OnProgress, это будет вашим сигналом о том, что поток завершен, и вы можете сбросить все в форме.

Однако вы можете подумать о радикальном изменении мышления.Я думаю, что методология Control.Invoke слишком злоупотребляет.Вместо использования Control.Invoke для перенаправления выполнения обработчика событий обратно в поток пользовательского интерфейса вы можете провести опрос потока пользовательского интерфейса для получения информации о ходе выполнения с использованием таймера.Когда появится новая информация о прогрессе, работник опубликует ее в некоторой переменной или структуре данных.Это имеет несколько преимуществ.

  • Это нарушает тесную связь между пользовательским интерфейсом и рабочими потоками, которые навязывает Control.Invoke.
  • Это возлагает ответственность за обновление потока пользовательского интерфейса на поток пользовательского интерфейса.где он должен принадлежать в любом случае.
  • Поток пользовательского интерфейса должен определять, когда и как часто должно происходить обновление.
  • Нет риска переполнения насоса сообщений пользовательского интерфейса, как в случаес методами маршалинга, инициированными рабочим потоком.
  • Рабочему потоку не нужно ждать подтверждения того, что обновление было выполнено, прежде чем переходить к следующим шагам (т. е. вы получаете большую пропускную способность как для пользовательского интерфейса, так и для пользовательского интерфейса).рабочие темы).
3 голосов
/ 15 июня 2011

Просто замените Invoke на BeginInvoke. Если это невозможно и должно быть синхронным, воспроизведите трюк DoEvents из этой статьи: http://www.codeproject.com/KB/cs/workerthread.aspx.

Замените t.Join () на этот цикл:

for(;;)
{
    if ( t.Join(100) )   // set appropriate timeout
    {
       break;
    }

    Application.DoEvents();    // resolve deadlock
}
0 голосов
/ 16 июня 2011

Не используйте Join в пользовательском интерфейсе!Это прекрасный пример того, как люди могут сломать любую модель программирования ... Чистый способ отменить фоновый работник - установить CancellationTokenSource и передать фоновому рабочему экземпляр CancellationToken.Вы вызываете CancellationTokenSource.Cancel на установленном экземпляре, если фоновая операция должна быть отменена.Затем вы проверяете значение токена по своему желанию и просто регулярно покидаете текущий путь выполнения.Я бы также рекомендовал использовать высокоуровневые асинхронные API (Задачи, APM) вместо порождения потоков вручную.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...