Ich finde, dass das .NET-Ereignismodell so ist, dass ich häufig ein Ereignis in einem Thread auslöse und es in einem anderen Thread abhöre. Ich habe mich gefragt, wie ich ein Ereignis am saubersten von einem Hintergrund-Thread auf meinen UI-Thread übertragen kann.
Basierend auf den Community-Vorschlägen habe ich Folgendes verwendet:
// 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
}
c#
multithreading
events
Nick
quelle
quelle
Antworten:
Einige Beobachtungen:
BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
Außerdem müssen Sie das Objektarray nicht erstellen und füllen, da der Parameter args vom Typ "params" ist, sodass Sie die Liste einfach übergeben können.
Ich würde wahrscheinlich begünstigen
Invoke
überBeginInvoke
wie dieser führt in den Code asynchron kann , die aufgerufen wird , oder kann nicht sein , was Sie nach , aber würden nachfolgende Ausnahmen schwer ohne Aufruf zu verbreiten machen HandhabungEndInvoke
. Was passieren würde, ist, dass Ihre AppTargetInvocationException
stattdessen eine bekommt .quelle
Ich habe einen Code dafür online. Es ist viel schöner als die anderen Vorschläge; Probieren Sie es auf jeden Fall aus.
Beispielnutzung:
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. }); }
quelle
System.Windows.Forms
in Ihrer Erweiterung ändern . Auf diese Weise vermeiden Sie, dass Sie Ihren benutzerdefinierten Namespace jedes Mal hinzufügen, wenn Sie ihn benötigen.Ich meide überflüssige Delegiertenerklärungen.
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 }
Für Nicht-Ereignisse können Sie den
System.Windows.Forms.MethodInvoker
Delegaten oder verwendenSystem.Action
.BEARBEITEN: Zusätzlich hat jede Veranstaltung einen entsprechenden
EventHandler
Delegierten, sodass es überhaupt nicht erforderlich ist, einen neu zu deklarieren.quelle
Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
Ich habe die folgende "universelle" Cross-Thread-Aufrufklasse für meinen eigenen Zweck erstellt, aber ich denke, es lohnt sich, sie zu teilen:
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; } } }
Und Sie können einfach SetAnyProperty () aus einem anderen Thread verwenden:
CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());
In diesem Beispiel führt die obige KvaserCanReader-Klasse einen eigenen Thread aus und ruft auf, um die Texteigenschaft der Bezeichnung lb_Speed im Hauptformular festzulegen.
quelle
Ich denke, der sauberste Weg ist definitiv , die AOP-Route zu gehen. Nehmen Sie einige Aspekte vor, fügen Sie die erforderlichen Attribute hinzu, und Sie müssen die Thread-Affinität nie wieder überprüfen.
quelle
Verwenden Sie den Synchronisationskontext, wenn Sie ein Ergebnis an den UI-Thread senden möchten. Ich musste die Thread-Priorität ändern, also wechselte ich von der Verwendung von Thread-Pool-Threads (auskommentierter Code) und erstellte einen eigenen neuen Thread. Ich konnte weiterhin den Synchronisationskontext verwenden, um zurückzugeben, ob der Datenbankabbruch erfolgreich war oder nicht.
#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()); } }
quelle
Ich habe mich immer gefragt, wie teuer es ist, immer davon auszugehen, dass ein Aufruf erforderlich ist ...
private void OnCoolEvent(CoolObjectEventArgs e) { BeginInvoke((o,e) => /*do work here*/,this, e); }
quelle
Interessanterweise behandelt die Bindung von WPF das Marshalling automatisch, sodass Sie die Benutzeroberfläche an Objekteigenschaften binden können, die in Hintergrundthreads geändert wurden, ohne etwas Besonderes tun zu müssen. Dies hat sich für mich als große Zeitersparnis erwiesen.
In XAML:
<TextBox Text="{Binding Path=Name}"/>
quelle
Sie können versuchen, eine Art generische Komponente zu entwickeln, die einen SynchronizationContext als Eingabe akzeptiert und zum Aufrufen der Ereignisse verwendet.
quelle