Я обнаружил, что модель событий .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
}





Я избегаю повторяющихся деклараций делегатов.
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, поэтому нет необходимости повторно объявлять его.
Для меня это работало так: Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
@ToniAlmeida Да, это была опечатка в моем коде. Спасибо, что указали на это.
Я всегда задавался вопросом, насколько дорого всегда предполагает, что требуется invoke ...
private void OnCoolEvent(CoolObjectEventArgs e)
{
BeginInvoke((o,e) => /*do work here*/,this, e);
}
Выполнение BeginInvoke в потоке графического интерфейса приведет к тому, что рассматриваемое действие будет отложено до следующего раза, когда поток пользовательского интерфейса обработает сообщения Windows. В некоторых случаях это действительно может быть полезно.
Пара наблюдений:
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent),
sender,
args);
Также вам не нужно создавать и заполнять массив объектов, потому что параметр args является типом params, поэтому вы можете просто передать его в список.
Я, вероятно, предпочел бы Invoke, а не BeginInvoke, поскольку последний приведет к асинхронному вызову кода, который может быть или не быть тем, что вам нужно, но затруднит распространение обработки последующих исключений без вызова EndInvoke. Что может случиться, так это то, что ваше приложение получит вместо этого TargetInvocationException.
Вы можете попытаться разработать какой-то общий компонент, который принимает SynchronizationContext в качестве входных данных и использует его для вызова событий.
Интересно отметить, что привязка WPF обрабатывает маршалинг автоматически, поэтому вы можете привязать пользовательский интерфейс к свойствам объекта, которые изменяются в фоновых потоках, без необходимости делать что-либо особенное. Это оказалось для меня отличной экономией времени.
В XAML:
<TextBox Text = "{Binding Path=Name}"/>
это не сработает. как только вы установите опору в потоке без пользовательского интерфейса, вы получите исключение .. т.е. Name = "gbc" bang! неудача ... нет бесплатного сырного товарища
Это не бесплатно (требует времени выполнения), но механизм связывания wpf, похоже, автоматически обрабатывает межпоточный маршаллинг. Мы часто используем это с реквизитами, которые обновляются сетевыми данными, полученными в фоновых потоках. Здесь есть объяснение: blog.lab49.com/archives/1166
@gbc Aaaaand объяснение исчезло 404.
У меня какой-то код для этого в сети. Это намного лучше, чем другие предложения; обязательно зацените.
Пример использования:
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 в своем расширении. Таким образом вы избегаете добавления ваше собственное пространство имен каждый раз, когда он вам нужен.
Я думаю, что самый чистый способ - это определенно, чтобы пойти по маршруту АОП. Сделайте несколько аспектов, добавьте необходимые атрибуты, и вам больше не придется проверять сходство потоков.
Я не понимаю твоего предложения. C# не является изначально аспектно-ориентированным языком. Вы имеете в виду какой-то шаблон или библиотеку для реализации аспектов, которые негласно реализуют маршалинг?
Я использую PostSharp, поэтому я определяю поведение потоковой передачи в классе атрибутов, а затем использую, скажем, атрибут [WpfThread] перед каждым методом, который должен быть вызван в потоке пользовательского интерфейса.
Я сделал следующий универсальный класс вызовов перекрестных потоков для своих целей, но думаю, что им стоит поделиться:
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 в основной форме.
Используйте контекст синхронизации, если вы хотите отправить результат в поток пользовательского интерфейса. Мне нужно было изменить приоритет потока, поэтому я отказался от потоков пула потоков (закомментированный код) и создал новый собственный поток. Я все еще мог использовать контекст синхронизации, чтобы узнать, была ли отмена базы данных успешной или нет.
#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());
}
}
Имейте в виду, что InvokeRequired может вернуть false, если у существующего управляемого элемента управления еще нет неуправляемого дескриптора. Вам следует проявлять осторожность в событиях, которые возникнут до того, как контроль будет полностью создан.