Frage

Ich finde, dass das .NET-Ereignismodell so ist, dass ich oft ein Ereignis in einem Thread auslöse und in einem anderen Thread darauf warte.Ich habe mich gefragt, wie ich ein Ereignis am saubersten von einem Hintergrundthread in meinen UI-Thread leiten 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
}
War es hilfreich?

Lösung

Ein paar Beobachtungen:

  • Erstellen Sie einfache Delegaten nicht explizit in einem solchen Code, es sei denn, Sie haben eine Version vor 2.0, also könnten Sie Folgendes verwenden:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • Außerdem müssen Sie das Objektarray nicht erstellen und füllen, da der args-Parameter vom Typ „params“ ist und Sie einfach die Liste übergeben können.

  • Ich würde es wahrscheinlich bevorzugen Invoke über BeginInvoke da letzteres dazu führt, dass der Code asynchron aufgerufen wird, was möglicherweise das ist, was Sie suchen, aber die Weitergabe nachfolgender Ausnahmen ohne einen Aufruf erschweren würde EndInvoke.Was passieren würde, wäre, dass Ihre App am Ende eine bekommt TargetInvocationException stattdessen.

Andere Tipps

Ich habe etwas Code dafür online.Es ist viel schöner als die anderen Vorschläge;Schauen Sie es sich auf jeden Fall an.

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

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 die verwenden System.Windows.Forms.MethodInvoker Delegierter bzw System.Action.

BEARBEITEN:Darüber hinaus gibt es zu jeder Veranstaltung eine entsprechende EventHandler delegieren, sodass überhaupt keine Notwendigkeit besteht, einen erneut zu deklarieren.

Ich habe die folgende „universelle“ Cross-Thread-Call-Klasse für meine eigenen Zwecke 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 ihren eigenen Thread aus und ruft auf, um die Texteigenschaft der Bezeichnung lb_Speed ​​im Hauptformular festzulegen.

Ich denke, der sauberste Weg ist definitiv den AOP-Weg gehen.Erstellen Sie ein paar Aspekte, fügen Sie die erforderlichen Attribute hinzu und Sie müssen die Thread-Affinität nie wieder überprüfen.

Verwenden Sie den Synchronisierungskontext, 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 den Synchronisierungskontext weiterhin 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());

        }
    }

Ich habe mich immer gefragt, wie teuer es ist stets Gehen Sie davon aus, dass ein Aufruf erforderlich ist ...

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

Als interessante Randbemerkung: Die Bindung von WPF übernimmt 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ße Zeitersparnis erwiesen.

In XAML:

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

Sie können versuchen, eine Art generische Komponente zu entwickeln, die a akzeptiert SynchronizationContext als Eingabe und verwendet sie zum Aufrufen der Ereignisse.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top