Найчистіший спосіб викликати перехресні нитки


79

Я вважаю, що модель подій .NET така, що я часто буду піднімати подію в одному потоці та слухати її в іншому. Мені було цікаво, який найчистіший спосіб перенести подію з фонового потоку на мій інтерфейс користувача.

На основі пропозицій спільноти я використав це:

// earlier in the code
mCoolObject.CoolEvent+= 
           new CoolObjectEventHandler(mCoolObject_CoolEvent);
// then
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        CoolObjectEventHandler cb =
            new CoolObjectEventHandler(
                mCoolObject_CoolEvent);
        Invoke(cb, new object[] { sender, args });
        return;
    }
    // do the dirty work of my method here
}

Майте на увазі, що InvokeRequired може повернути значення false, коли існуючий керований елемент керування ще не має керованого дескриптора. Ви повинні проявляти обережність у випадках, які будуть підняті до того, як контроль буде повністю створений.
— GregC

Відповіді:


28

Пара спостережень:

  • Не створюйте простих делегатів явно в такому коді, якщо ви не користуєтесь попередньою версією 2.0, щоб ви могли використовувати:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Також вам не потрібно створювати та заповнювати масив об'єктів, оскільки параметр args має тип "params", тому ви можете просто передати його у списку.

  • Я б , ймовірно , в користь Invokeбільш , BeginInvokeяк останній результат буде в коді викликається асинхронно , який може або не може бути те , що ви після цього, але буде робити обробку наступних винятків важко поширюватися без виклику EndInvoke. Що могло б статися, так це те, що ваш додаток в кінцевому підсумку отримає TargetInvocationExceptionнатомість.


44

У мене є якийсь код для цього Інтернеті. Це набагато приємніше, ніж інші пропозиції; однозначно перевірити це.

Зразок використання:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    // You could use "() =>" in place of "delegate"; it's a style choice.
    this.Invoke(delegate
    {
        // Do the dirty work of my method here.
    });
}

Ви також можете змінити простір імен System.Windows.Formsу своєму розширенні. Таким чином ви уникаєте додавання власного простору імен кожного разу, коли вам це потрібно.
— Джо Алмор

10

Я уникаю зайвих оголошень делегатів.

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
        return;
    }
    // do the dirty work of my method here
}

Для не подій можна використовувати System.Windows.Forms.MethodInvokerделегат або System.Action.

РЕДАГУВАТИ: Крім того, кожна подія має відповідного EventHandlerделегата, тому взагалі не потрібно повторно оголошувати одного.


1
Для мене це працювало так:Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
— Антоніо Алмейда

@ToniAlmeida Так, це була помилка в моєму коді. Дякуємо, що вказали на це.
— Конрад Рудольф

4

Я зробив наступний 'універсальний' клас виклику між потоками для власних цілей, але я думаю, що варто поділитися ним:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace CrossThreadCalls
{
  public static class clsCrossThreadCalls
  {
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value);
    public static void SetAnyProperty(Control c, string Property, object Value)
    {
      if (c.GetType().GetProperty(Property) != null)
      {
        //The given property exists
        if (c.InvokeRequired)
        {
          SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty);
          c.BeginInvoke(d, c, Property, Value);
        }
        else
        {
          c.GetType().GetProperty(Property).SetValue(c, Value, null);
        }
      }
    }

    private delegate void SetTextPropertyCallBack(Control c, string Value);
    public static void SetTextProperty(Control c, string Value)
    {
      if (c.InvokeRequired)
      {
        SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty);
        c.BeginInvoke(d, c, Value);
      }
      else
      {
        c.Text = Value;
      }
    }
  }

І ви можете просто використовувати SetAnyProperty () з іншого потоку:

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

У цьому прикладі вищезазначений клас KvaserCanReader запускає власний потік і робить виклик для встановлення властивості тексту мітки lb_Speed ​​в основній формі.


3

Я думаю, що найчистішим способом, безумовно, є пройти маршрут AOP. Зробіть кілька аспектів, додайте необхідні атрибути, і вам більше ніколи не доведеться перевіряти спорідненість ниток.


Я не розумію вашої пропозиції. C # не є власною мовою, орієнтованою на аспекти. Ви маєте на увазі якийсь шаблон чи бібліотеку для реалізації аспектів, що реалізують маршовий марш-марш?
— Ерік

Я використовую PostSharp, тому я визначаю поведінку потоків у класі атрибутів, а потім використовую, скажімо, атрибут [WpfThread] перед кожним методом, який потрібно викликати в потоці інтерфейсу користувача.
— Дмитро Нестерук

3

Використовуйте контекст синхронізації, якщо потрібно надіслати результат потоку інтерфейсу користувача. Мені потрібно було змінити пріоритет потоку, тому я перейшов від використання потоків пулу потоків (прокоментував код) і створив новий власний потік. Я все ще міг використовувати контекст синхронізації, щоб повернути, вдалося скасувати базу даних чи ні.

    #region SyncContextCancel

    private SynchronizationContext _syncContextCancel;

    /// <summary>
    /// Gets the synchronization context used for UI-related operations.
    /// </summary>
    /// <value>The synchronization context.</value>
    protected SynchronizationContext SyncContextCancel
    {
        get { return _syncContextCancel; }
    }

    #endregion //SyncContextCancel

    public void CancelCurrentDbCommand()
    {
        _syncContextCancel = SynchronizationContext.Current;

        //ThreadPool.QueueUserWorkItem(CancelWork, null);

        Thread worker = new Thread(new ThreadStart(CancelWork));
        worker.Priority = ThreadPriority.Highest;
        worker.Start();
    }

    SQLiteConnection _connection;
    private void CancelWork()//object state
    {
        bool success = false;

        try
        {
            if (_connection != null)
            {
                log.Debug("call cancel");
                _connection.Cancel();
                log.Debug("cancel complete");
                _connection.Close();
                log.Debug("close complete");
                success = true;
                log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
            }
        }
        catch (Exception ex)
        {
            log.Error(ex.Message, ex);
        }

        SyncContextCancel.Send(CancelCompleted, new object[] { success });
    }

    public void CancelCompleted(object state)
    {
        object[] args = (object[])state;
        bool success = (bool)args[0];

        if (success)
        {
            log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());

        }
    }

2

Я завжди дивувався, наскільки дорого завжди вважати, що потрібно викликати ...

private void OnCoolEvent(CoolObjectEventArgs e)
{
  BeginInvoke((o,e) => /*do work here*/,this, e);
}

1
Виконання BeginInvoke всередині потоку графічного інтерфейсу призведе до того, що розглянута дія буде відкладена до наступного разу, коли потік інтерфейсу обробляє повідомлення Windows. Насправді це може бути корисно зробити в деяких випадках.
— supercat

2

Як цікаву побічну ноту, прив’язка WPF автоматично обробляє маршалінг, так що ви можете прив’язати інтерфейс користувача до властивостей об’єктів, які змінені у фонових потоках, не роблячи нічого особливого. Це виявилось чудовою економією часу для мене.

У XAML:

<TextBox Text="{Binding Path=Name}"/>

це не працює. як тільки ви встановите проп для потоку, що не є інтерфейсом користувача, ви отримаєте виняток .. тобто Name = "gbc" bang! невдача ... немає безкоштовного сирного партнера
— Boppity Bop

Це не безкоштовно (це вимагає часу на виконання), але механізм прив'язки wpf, здається, автоматично обробляє міжпотокове маршування. Ми багато використовуємо це для реквізитів, які оновлюються мережевими даними, отриманими у фонових потоках. Тут є пояснення: blog.lab49.com/archives/1166
— gbc

1
@gbc Aaaaі пояснення зникло 404.
— Січень 'сплайт' К.

0

Ви можете спробувати розробити якийсь загальний компонент, який приймає SynchronizationContext як вхід і використовує його для виклику подій.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.