Frage Die sauberste Möglichkeit, Cross-Thread-Ereignisse aufzurufen


Ich finde, dass das .NET-Ereignismodell so ist, dass ich oft ein Ereignis in einem Thread auslösen und in einem anderen Thread darauf hören werde. Ich habe mich gefragt, was der sauberste Weg ist, ein Ereignis von einem Hintergrund-Thread auf meinen UI-Thread zu mappen.

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
}

75
2017-08-22 13:34


Ursprung


Antworten:


Ein paar Beobachtungen:

  • Erstellen Sie keine einfachen Delegaten explizit in Code wie diesem, es sei denn, Sie sind vor 2.0, so dass Sie Folgendes verwenden könnten:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Außerdem müssen Sie das Objektarray nicht erstellen und auffüllen, da der args-Parameter vom Typ "params" ist, sodass Sie die Liste einfach übergeben können.

  • Ich würde wahrscheinlich bevorzugen Invoke Über BeginInvoke da letzteres dazu führen wird, dass der Code asynchron aufgerufen wird, was möglicherweise nicht das ist, was Sie wollen, aber die Verarbeitung nachfolgender Ausnahmen schwierig machen würde, ohne einen Aufruf an zu propagieren EndInvoke. Was passieren würde, ist, dass Ihre App am Ende ein TargetInvocationException stattdessen.


25
2017-08-22 13:45



ich habe etwas Code dafür online. Es ist viel schöner als die anderen Vorschläge; Überprüfen Sie es auf jeden Fall.

Beispielverwendung:

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

41
2017-11-03 11:30



Ich meide redundante Delegatendeklarationen.

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 die System.Windows.Forms.MethodInvoker delegieren oder System.Action.

EDIT: Zusätzlich hat jedes Ereignis ein entsprechendes EventHandler Delegieren, so dass es überhaupt keinen Grund gibt, einen zu reklamieren.


11
2017-08-22 13:42



Ich denke, der sauberste Weg ist bestimmt um die AOP-Route zu gehen. Machen Sie ein paar Aspekte, fügen Sie die notwendigen Attribute hinzu und Sie müssen nie wieder die Thread-Affinität überprüfen.


3
2018-04-29 17:33



Ich habe die folgende "universelle" Cross-Thread-Call-Klasse 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 SetAnyProperty () einfach 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 des lb_Speed-Labels im Hauptformular festzulegen.


3
2017-10-05 09:36



Verwenden Sie den Synchronisierungskontext, wenn Sie ein Ergebnis an den UI-Thread senden möchten. Ich musste die Thread-Priorität ändern, also änderte ich Thread-Pool-Threads (auskommentierter Code) und erstellte einen neuen Thread. Ich war immer noch in der Lage, den Synchronisationskontext zu verwenden, um zurückzugeben, ob die Löschung der Datenbank 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());

        }
    }

3
2017-07-07 08:20



Als interessante Randbemerkung behandelt die Bindung von WPF das Marshalling automatisch, sodass Sie die Benutzeroberfläche an Objekteigenschaften binden können, die in Hintergrundthreads geändert werden, ohne dass Sie etwas Besonderes tun müssen. Das hat sich für mich als großer Zeitsparer erwiesen.

In XAML:

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

2
2017-08-22 14:20



Ich habe mich immer gefragt, wie teuer es ist immer Angenommen, dass Aufruf erforderlich ist ...

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

1
2017-08-22 13:44



Sie können versuchen, eine Art generische Komponente zu entwickeln, die ein akzeptiert Synchronisierungskontext als Eingabe und verwendet es, um die Ereignisse aufzurufen.


0
2017-08-22 13:53