Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

Чтобы упростить код Саймона, вы можете использовать встроенный общий делегат Action. Это избавляет вас от переполнения вашего кода кучей типов делегатов, которые вам действительно не нужны. Кроме того, в .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";
}

Ответ 2

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

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

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

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";
}

Это очень просто.

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

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

Ответ 3

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

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

Ответ 4

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

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

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; }
        }
    }
}

Ответ 5

Как 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.";
}

Ответ 6

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