Как мне сделать обратные вызовы событий в моих win forms потокобезопасными?

StackOverflow https://stackoverflow.com/questions/6184

Вопрос

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

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

Это было полезно?

Решение

Чтобы немного упростить код Саймона, вы могли бы использовать встроенный универсальный делегат действий.Это позволяет не перегружать ваш код кучей типов делегатов, которые вам на самом деле не нужны.Кроме того, в .NET 3.5 они добавили параметр params к методу Invoke, так что вам не нужно определять временный массив.

void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text = "Something happened";
}

Другие советы

Вот основные моменты:

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

Итак, если у вас есть отдельный поток "движка", выполняющий какую-то работу, и какой-то пользовательский интерфейс следит за изменениями состояния, которые могут быть отражены в пользовательском интерфейсе (например, индикатор выполнения или что-то еще), у вас проблема.Запуск двигателя - это событие изменения объекта, которое было подключено Формой.Но делегат обратного вызова, который регистрирует Форму в движке, вызывается в потоке движка… а не в потоке Формы.И поэтому вы не можете обновить какие-либо элементы управления из этого обратного вызова.Дох!

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

private delegate void EventArgsDelegate(object sender, EventArgs ea);

void SomethingHappened(object sender, EventArgs ea)
{
   //
   // Make sure this callback is on the correct thread
   //
   if (this.InvokeRequired)
   {
      this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
      return;
   }

   //
   // Do something with the event such as update a control
   //
   textBox1.Text = "Something happened";
}

На самом деле все довольно просто.

  1. Использование Требуемый вызов чтобы выяснить, произошел ли этот обратный вызов в правильном потоке.
  2. Если нет, то повторно вызовите обратный вызов в правильном потоке с теми же параметрами.Вы можете повторно запустить метод, используя Призывать (блокирующий) или BeginInvoke начать вызывать (неблокирующие) методы.
  3. При следующем вызове функции, Требуемый вызов возвращает false, потому что теперь мы находимся в нужном потоке, и все довольны.

Это очень компактный способ решения этой проблемы и обеспечения безопасности ваших форм от многопоточных обратных вызовов событий.

В этом сценарии я часто использую анонимные методы:

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}

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

Вот краткий пример, который допускает только один одновременный вызов;поддержка нескольких вызовов / событий требует немного больше сантехники.

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

namespace WindowsFormsApplication1
{
    public class MainForm : Form
    {
        private TypeWithAsync _type;

        [STAThread()]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new MainForm());
        }

        public MainForm()
        {
            _type = new TypeWithAsync();
            _type.DoSomethingCompleted += DoSomethingCompleted;

            var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };

            var btn = new Button() { Text = "Synchronous" };
            btn.Click += SyncClick;
            panel.Controls.Add(btn);

            btn = new Button { Text = "Asynchronous" };
            btn.Click += AsyncClick;
            panel.Controls.Add(btn);

            Controls.Add(panel);
        }

        private void SyncClick(object sender, EventArgs e)
        {
            int value = _type.DoSomething();
            MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
        }

        private void AsyncClick(object sender, EventArgs e)
        {
            _type.DoSomethingAsync();
        }

        private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
        {
            MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
        }
    }

    class TypeWithAsync
    {
        private AsyncOperation _operation;

        // synchronous version of method
        public int DoSomething()
        {
            Thread.Sleep(5000);
            return 27;
        }

        // async version of method
        public void DoSomethingAsync()
        {
            if (_operation != null)
            {
                throw new InvalidOperationException("An async operation is already running.");
            }

            _operation = AsyncOperationManager.CreateOperation(null);
            ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
        }

        // wrapper used by async method to call sync version of method, matches WaitCallback so it
        // can be queued by the thread pool
        private void DoSomethingAsyncCore(object state)
        {
            int returnValue = DoSomething();
            var e = new DoSomethingCompletedEventArgs(returnValue);
            _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
        }

        // wrapper used so async method can raise the event; matches SendOrPostCallback
        private void RaiseDoSomethingCompleted(object args)
        {
            OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
        }

        private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
        {
            var handler = DoSomethingCompleted;

            if (handler != null) { handler(this, e); }
        }

        public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
    }

    public class DoSomethingCompletedEventArgs : EventArgs
    {
        private int _value;

        public DoSomethingCompletedEventArgs(int value)
            : base()
        {
            _value = value;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}

В качестве lazy programmer, У меня есть очень ленивый метод сделать это.

То, что я делаю, - это просто вот что.

private void DoInvoke(MethodInvoker del) {
    if (InvokeRequired) {
        Invoke(del);
    } else {
        del();
    }
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
    DoInvoke(delegate { lbl.Text = val; });
}

Вы могли бы встроить DoInvoke в свою функцию или скрыть его в отдельной функции, чтобы она выполняла грязную работу за вас.

Просто имейте в виду, что вы можете передавать функции непосредственно в метод DoInvoke.

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}

Во многих простых случаях вы можете использовать делегат MethodInvoker и избежать необходимости создавать свой собственный тип делегата.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top