Я часто использую этот шаблон при создании частных внутренних классов для упрощения моего кода, но я не рекомендовал бы представить такие объекты в общедоступном API. В целом, чем более часто можно сделать объекты в общедоступном API неизменными, тем лучше, и не возможно создать 'подобный структуре' объект неизменным способом.
Как в стороне, даже если бы я писал этот объект как частный внутренний класс, который я все еще предоставил бы конструктору для упрощения кода для инициализации объекта. Необходимость иметь 3 строки кода для получения применимого объекта, когда каждый сделает, просто грязна.
Ваша библиотека может проверять цель каждого делегата в списке вызовов события и маршалировать вызов целевого потока, если эта цель - ISynchronizeInvoke:
private void RaiseEventOnUIThread(Delegate theEvent, object[] args)
{
foreach (Delegate d in theEvent.GetInvocationList())
{
ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke;
if (syncer == null)
{
d.DynamicInvoke(args);
}
else
{
syncer.BeginInvoke(d, args); // cleanup omitted
}
}
}
Другой подход, который делает контракт потоковой передачи более явным является требование, чтобы клиенты вашей библиотеки передавали ISynchronizeInvoke или SynchronizationContext для потока, в котором они хотят, чтобы вы вызывали события. Это дает пользователям вашей библиотеки немного больше видимости и контроля, чем подход «тайной проверки цели делегата».
Что касается вашего второго вопроса, я бы поместил материал для маршаллинга потоков в ваш OnXxx или любой другой API, который вызывает код пользователя. это могло привести к возникновению события.
Вы можете сохранить диспетчер для основного потока в своей библиотеке, использовать его для проверки того, работаете ли вы в потоке пользовательского интерфейса, и при необходимости выполнять в потоке пользовательского интерфейса через него.
Документация по потокам WPF содержит хорошее введение и примеры того, как это сделать.
Вот его суть:
private Dispatcher _uiDispatcher;
// Call from the main thread
public void UseThisThreadForEvents()
{
_uiDispatcher = Dispatcher.CurrentDispatcher;
}
// Some method of library that may be called on worker thread
public void MyMethod()
{
if (Dispatcher.CurrentDispatcher != _uiDispatcher)
{
_uiDispatcher.Invoke(delegate()
{
// UI thread code
});
}
else
{
// UI thread code
}
}
Вы можете использовать класс SynchronizationContext для маршалинга вызовов потока пользовательского интерфейса в WinForms или WPF с помощью SynchronizationContext.Current
.
Вот идея Itwolson, выраженная в качестве метода расширения, который отлично работает для меня:
/// <summary>Extension methods for EventHandler-type delegates.</summary>
public static class EventExtensions
{
/// <summary>Raises the event (on the UI thread if available).</summary>
/// <param name="multicastDelegate">The event to raise.</param>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An EventArgs that contains the event data.</param>
/// <returns>The return value of the event invocation or null if none.</returns>
public static object Raise(this MulticastDelegate multicastDelegate, object sender, EventArgs e)
{
object retVal = null;
MulticastDelegate threadSafeMulticastDelegate = multicastDelegate;
if (threadSafeMulticastDelegate != null)
{
foreach (Delegate d in threadSafeMulticastDelegate.GetInvocationList())
{
var synchronizeInvoke = d.Target as ISynchronizeInvoke;
if ((synchronizeInvoke != null) && synchronizeInvoke.InvokeRequired)
{
retVal = synchronizeInvoke.EndInvoke(synchronizeInvoke.BeginInvoke(d, new[] { sender, e }));
}
else
{
retVal = d.DynamicInvoke(new[] { sender, e });
}
}
}
return retVal;
}
}
Вы тогда просто поднимаете свое мероприятие:
MyEvent.Raise(this, EventArgs.Empty);