Какова правильная процедура, чтобы предупредить поток остановить, что он делает и вернуться перед выходом? - PullRequest
1 голос
/ 07 октября 2019

Какова правильная процедура для оповещения работающего потока о прекращении его работы и возврата перед выходом из приложения?

protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;

public Form1()
{
    InitializeComponent();
    T = new Thread(ThreadFunc);
    T.Start();
}

private void ThreadFunc()
{
    while (ThreadRunning)
    {
        // Do stuff
        Thread.Sleep(40);
    }
    mre.Set();
}

private void ExitButton_Click(object sender, EventArgs e)
{
    ThreadRunning = false;
    mre.WaitOne();
    mre.Close();
    Application.Exit();
}

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

  1. Установите ThreadRunning = false так, чтобы в следующий раз поток T проверял эту переменную, которую он знает, чтобы остановить.
  2. Вызов mre.WaitOne() для ожиданиячтобы поток T мог сказать, что на самом деле это делается с помощью вызова mre.Set().
  3. Если это так, разблокируйте и продолжайте, избавьтесь от mre (mre.Close()) и выйдите.

Дляпо какой-то причине вышеописанная настройка иногда дает сбой после нажатия кнопки выхода, и вся форма становится неактивной.

Моя новая настройка приведена ниже, но мне она не совсем корректна, например, mre.Set() не идетждать чего-либо и Application.Exit() сразу после этого. Я просто жду, пока он потерпит неудачу, как и раньше, но пока этого не произошло.

protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;

public Form1()
{
    InitializeComponent();
    T = new Thread(ThreadFunc);
    T.Start();
}

private void ThreadFunc()
{
    while (ThreadRunning)
    {
        // Do stuff
        Thread.Sleep(40);
    }
    mre.WaitOne();
    mre.Close();
}

private void ExitButton_Click(object sender, EventArgs e)
{
    ThreadRunning = false;
    mre.Set();
    Application.Exit();
}

Ответы [ 3 ]

1 голос
/ 07 октября 2019

Пользовательский интерфейс зависает, потому что вы блокируете поток пользовательского интерфейса с помощью mre.WaitOne();. Если вам нужно дождаться завершения потока, вы можете использовать его свойство IsAlive и обрабатывать сообщения приложения, для этого вам не нужны события приложения:

while(_t.IsAlive)
  Application.DoEvents();

Существует 2 отмены потокаaproaches:

  • кооператив - код, выполняемый потоком, знает, что его можно отменить и обработать отменой изящно, вот что вы пытаетесь сделать здесь.
  • обязательно - принудительно остановить поток - вызовите Thread.Abort или Interrupt, , не используйте , что.

Как упоминалось @HansPassant, bool - не лучший вариантпотому что этот компилятор может оптимизировать это, и значение bool может быть кэшировано, и его изменение не может быть обработано зацикливанием потока. Вам нужно сделать его по крайней мере volatile или просто изменить код для использования CancellationSource.

Учитывая, что делает ваш поток, возможно, BackgroundWorker, Timer или шаблон Производитель / Потребитель - лучшая альтернативана Thread, но у меня слишком мало контекста, чтобы что-то рекомендовать. Также он работает хорошо только в том случае, если у вас есть только 1 экземпляр Form1 в приложении, если у вас есть мультиформное приложение, и пользователь может открыть несколько форм Form1, у вас будут проблемы.

Общие рекомендации, еслиВы можете работать с полями уровня экземпляра, пожалуйста, не используйте static.

0 голосов
/ 07 октября 2019

Ожидание 40 мсек между выполнением заданий не создает проблем, но что если вам пришлось ждать 5 с или более? Тогда отмена между каждого ожидания будет проблематичной, и правильнее всего будет отменить самого ожидающего . Это довольно легко сделать на самом деле. Просто замените Thread.Sleep(40) на Task.Delay(40, token).Wait(), где token - это CancellationToken.

class Form1 : Form
{
    protected readonly CancellationTokenSource _cts;
    protected readonly Thread _thread;

    public Form1()
    {
        InitializeComponent();
        _cts = new CancellationTokenSource();
        _thread = new Thread(ThreadFunc);
        _thread.Start();
    }

    private void ThreadFunc()
    {
        try
        {
            while (true)
            {
                // Do stuff here
                Task.Delay(40, _cts.Token).GetAwaiter().GetResult();
            }
        }
        catch (OperationCanceledException)
        {
            // Ignore cancellation exception
        }
    }

    private void ExitButton_Click(object sender, EventArgs e)
    {
        _cts.Cancel();
        this.Visible = false; // Hide the form before blocking the UI
        _thread.Join(5000); // Wait the thread to finish, but no more than 5 sec
        this.Close();
    }
}

Лично я предпочел бы выполнять фоновую работу, используя Task вместоThread, потому что его легче ожидать, не блокируя пользовательский интерфейс. Эта задача будет выполняться лениво с использованием потоков пула потоков. Недостатком является то, что материал, который запускается каждые 40 мсек, не всегда будет работать в одном потоке, поэтому у меня могут возникнуть проблемы с безопасностью потока.

class Form1 : Form
{
    protected readonly CancellationTokenSource _cts;
    protected readonly Task _task;

    public Form1()
    {
        InitializeComponent();
        _cts = new CancellationTokenSource();
        _task = Task.Run(TaskFunc);
        this.FormClosing += Form_FormClosing;
    }

    private async Task TaskFunc()
    {
        try
        {
            while (true)
            {
                // Do async stuff here, using _cts.Token if possible
                // The stuff will run in thread-pool threads
                await Task.Delay(40, _cts.Token).ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException)
        {
            // Ignore cancellation exception
        }
    }

    private void ExitButton_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    private async void Form_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (_task == null || _task.IsCompleted) return;
        e.Cancel = true;
        _cts.Cancel();
        this.Visible = false; // Or give feedback that the form is closing
        var completedTask = await Task.WhenAny(_task, Task.Delay(5000));
        if (completedTask != _task) Debug.WriteLine("Task refuses to die");
        _task = null;
        await Task.Yield(); // To ensure that Close won't be called synchronously
        this.Close(); // After await we are back in the UI thread
    }
}
0 голосов
/ 07 октября 2019

Вы можете использовать тот же шаблон условной переменной, используя Thread.IsAlive, например:

using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsAppTest
{

  public partial class FormTest : Form
  {

    static volatile protected bool CancelRequired = false;
    protected Thread TheThread;
    public FormTest()
    {
      InitializeComponent();
      TheThread = new Thread(ThreadFunc);
      TheThread.Start();
    }
    private void ButtonAction_Click(object sender, EventArgs e)
    {
      CancelRequired = true;
      while ( TheThread.IsAlive ) 
        Application.DoEvents();
      Application.Exit();
    }
    private void ThreadFunc()
    {
      while ( !CancelRequired )
      {
        // Do stuff
        Thread.Sleep(40);
      }
    }
  }

}

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