Как обратится к элементу формы из обработчика события, которое вызывается в другом потоке?

367
15 октября 2017, 20:35

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

class class1
{
   //событие на которое вешается SomeEventHandler
   public event EventHandler event1;
   public class1()
{
   new Thread(Method1).Start();
}
private void Method1()
{
    //выполняется в фоновом потоке
    event1?.Invoke();
}
}
class Form1
{
    SomeEventHandler()
    {
         //взаимодействие с формой
         //сюда нужно добавить что то, что будет взаимодействовать с формой но из основного потока
    }
}

Как видно SomeEventHandler будет вызван в другом потоке, но тогда в этом обработчике нельзя обратится к контролу формы. Как это обойти?

Конструктор class1 тоже запускается не в основном потоке, не из интерфейса.

Answer 1

Экземпляр класса Worker в данном примере должен создаваться в UI потоке, потому что в его конструкторе мы получаем контекст синхронизации .

Если не хочется в класс, содержащий код, который работает в другом потоке пихать контекст синхронизации, то просто внутри метода обработчика в классе окна (SomeEventHandler)вызывай метод Invoke (кстати, там ответ написал Джон Скит - автор книг по C# и .NET технологиям)

В WPF и Windows Forms разные реализации SynchronizationContext. То есть в зависимости от технологии он по разному перенаправляет выполнение в UI поток (если речь о UI).

Заметь, что без

_sycnContext = SynchronizationContext.Current ?? new SynchronizationContext();

В консольном приложении не заработает, так как SynchronizationContext.Current там будет null.

Worker.cs

using System;
using System.Threading;
namespace WorkerLib
{
    public class Worker
    {
        private readonly SynchronizationContext _syncContext;
        public Worker()
        {
            _syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
        }
        public event EventHandler Started;
        public event EventHandler Completed;
        private void StartOperation(object state)
        {
            _syncContext.Post(OnStarted, null);
            // Делаем работу
            Thread.Sleep(1250);
            _syncContext.Post(OnCompleted, null);
        }
        public void DoWork()
        {
            ThreadPool.QueueUserWorkItem(StartOperation);
        }
        private void OnCompleted(object state)
        {
            var handler = Completed;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
        private void OnStarted(object state)
        {
            var handler = Started;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
    }
}

Windows Forms

MainForm.cs

using System;
using System.Windows.Forms;
using WorkerLib;
namespace WinForms_SyncContextExample
{
    public partial class MainForm : Form
    {
        private readonly Worker _worker;
        public MainForm()
        {
            InitializeComponent();
            _worker = new Worker();
            _worker.Started += (sender, args) => infoLabel.Text = "Запустили работу";
            _worker.Completed += (sender, args) => infoLabel.Text = "Работа завершена";
        }
        private void WorkButton_Click(object sender, EventArgs e)
        {
            _worker.DoWork();
        }
    }
}

Console

Program.cs

using System;
using WorkerLib;
namespace SyncContextExample
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Worker worker = new Worker();
            worker.Started += (sender, args) => Console.WriteLine("Запустили работу");
            worker.Completed += (sender, args) => Console.WriteLine("Работа завершена");
            worker.DoWork();
            Console.ReadKey();
        }
    }
}
Answer 2

Создайте синглтон, общий для всех потоков, и положите в него экземпляр вашей формы. Тогда другой поток сможет взаимодействовать с ней по ссылке.

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

READ ALSO
Windows Forms Application в Rider

Windows Forms Application в Rider

В Visual Studio есть Windows Forms ApplicationА есть ли это в Rider? Никак не могу найти

284
Прерывание выполнения метода “А” на время выполнения метода “Б”

Прерывание выполнения метода “А” на время выполнения метода “Б”

Доброго времени сутокПытаюсь реализовать очередь на отправку сообщений

182
Не может обработать исключение AccessViolationException

Не может обработать исключение AccessViolationException

Вот тут вызывается сам nativeCall

269