7
votes

Événement incendie de la composante ASYNC dans le fil d'interface utilisateur

Je construis un composant non visuel de .NET 2.0. Ce composant utilise une prise asynchrone (BeNreceive, Endreceive, etc.). Les rappels asynchrones sont appelés dans le contexte d'un fil de travail créé par le temps d'exécution. L'utilisateur du composant ne devrait pas avoir à s'inquiéter de multithreading (c'est l'objectif principal, ce que je veux)

L'utilisateur du composant peut créer mon composant non visuel dans n'importe quel thread (le thread de l'interface utilisateur est juste un thread commun pour des applications simples . Des applications plus graves pourraient créer le composant dans un fil de travail arbitraire). Les événements de déclenchement des composants tels que "SéanceConnected" ou "DataVailable". P>

Le problème: En raison des rappels ASYNC et des événements soulevés, le gestionnaire d'événements est exécuté dans le contexte du fil de travail. Je veux utiliser une couche intermédiaire qui force le gestionnaire d'événements à exécuter dans le contexte du fil qui a créé la composant à la première place. p>

exemple de code (dépouillé de la manipulation d'exception, etc.) p> xxx pré>

en raison de la nature des prises ASYNC Toutes les applications L'utilisation de mon composant est jonchée de "if (this.invokéréquiitéd) {..." et tout ce que je veux, c'est l'utilisateur de pouvoir utiliser mon composant sans souci comme une sorte de goutte. p>

Alors, comment allez-y surveiller les événements sans demander à l'utilisateur de vérifier invoquée (ou, de mettre différemment, comment puis-je forcer les événements soulevés dans le même fil que le fil qui a initié l'événement en premier lieu)? P>

J'ai lu des choses à propos de l'asyncopération, des travailleurs de fond, de la synchronisation, des asynccallbacks et des tonnes d'autres choses, mais tout fait tourner la tête. P>

Je suis venu avec cela, sûrement maladroit, "solution", mais elle Semble échouer dans certaines situations (lorsque mon composant est appelé à partir d'un projet WinForms via une classe statique par exemple) P>

    /// <summary>
    /// Raises an event, ensuring BeginInvoke is called for controls that require invoke
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    /// <remarks>http://www.eggheadcafe.com/articles/20060727.asp</remarks>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            try
            {
                Control ed = eventDelegate.Target as Control;
                if ((ed != null) && (ed.InvokeRequired))
                    ed.Invoke(eventDelegate, args);
                else
                    eventDelegate.DynamicInvoke(args);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.GetType());
                Console.WriteLine(ex.Message);
                //Swallow
            }
        }
    }


0 commentaires

4 Réponses :


0
votes

Peut-être que je ne comprends peut-être pas la question, mais il me semble que vous pouvez simplement transmettre une référence à un objet personnalisé dans votre état asynchronisé.

Je mets ensemble l'exemple suivant pour illustrer; p> Nous avons d'abord un objet de rappel. Ceci a 2 propriétés - un contrôle sur lequel envoyer des actions et une action à appeler; p> xxx pré>

alors j'ai un projet WinForms qui appelle un code aléatoire sur un autre thread (en utilisant Begininvoke) puis affiche une messagerie-matine lorsque le code termine l'exécution de l'exécution. P> xxx pré>

La méthode showMessagebox doit exécuter sur le fil de l'interface utilisateur et ressemble à: p>

    private void ShowMessageBox()
    {
        MessageBox.Show("Testing");
    }


1 commentaires

Pas vraiment; C'est un moyen de complexe pour les personnes lors de l'utilisation de mon composant. Tout ce que fait est référence le composant et utilisez le code comme: // initialise MyComponent avec IP, Port, etc., puis: MyComponent.Sandring ("Ceci est cool"); Le MyComponent soulève des événements; Qu'ils soient traités par un projet d'interface graphique ou de non-GUI n'auront pas d'importance et je ne veux pas que l'utilisateur soit responsable de vérifier invoquée.



0
votes

Si votre composant doit toujours être utilisé par le même thread, vous pouvez faire quelque chose comme ceci: xxx

puis lorsque vous instaniez votre composant d'un formulaire ou d'un autre contrôle, vous pouvez le faire: xxx

à la file d'attente de l'événement sur un fil de travail non UI, il doit avoir une sorte de mécanisme de file d'attente de travail, vous pouvez alors donner une méthode avec la signature de Callbackinvoker pour faire la queue du délégué sur le fil du travailleur.


3 commentaires

Cela met trop de complexité sur les genoux de l'utilisateur en utilisant mon composant.


Où est cette complexité? Spécification d'un délégué dans le constructeur du composant?


Je veux que ma composante soit transparente aussi asynchrone (afin que l'enduser ne remarque pas). Lorsque l'enduser doit transmettre un délégué pour aucune raison (évidente), je pense que ce n'est pas un "candidat". Il me semble avoir trouvé ma solution; Mais je ne suis pas sûr que c'est "la meilleure" solution.



1
votes

J'ai semblé avoir trouvé ma solution:

    private SynchronizationContext _currentcontext

    /// Constructor of my component:
    MyComponent() {
        _currentcontext = WindowsFormsSynchronizationContext.Current;
       //...or...?
        _currentcontext = SynchronizationContext.Current;
    }

    /// <summary>
    /// Raises an event, ensuring the correct context
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            if (_currentcontext != null)
                _currentcontext.Post(new System.Threading.SendOrPostCallback(
                    delegate(object a)
                    {
                        eventDelegate.DynamicInvoke(a as object[]);
                    }), args);
            else
                eventDelegate.DynamicInvoke(args);
        }
    }


4 commentaires

Que se passera-t-il avec cette approche lorsque votre composant n'est pas créé sur le fil de l'interface utilisateur?


J'ai oublié de coller ma dernière modification, qui vérifie si _CurrentContext est NULL; Ceci est maintenant corrigé (et édité).


Je viens de vérifier, si vous capturez la synchronisationContext dans le constructeur du composant et que le composant est créé dans un thread non UI, votre eventDelegate sera exécuté sur le threadpool.


J'utilise WindowsformsSynchronizationContext maintenant (j'ai éditos de ma "solution"). Cela fera-t-il une différence? Je suis assez désemparé sur ce sujet que j'ai peur. J'ai vérifié cela avec une application de console et avec une application WinForms et les deux semblent fonctionner correctement.



2
votes

OK; Voici donc ce que je me suis retrouvé après une nouvelle lecture:

public class MyComponent {
    private AsyncOperation _asyncOperation;

    /// Constructor of my component:
    MyComponent() {
        _asyncOperation = AsyncOperationManager.CreateOperation(null);
    }

    /// <summary>
    /// Raises an event, ensuring the correct context
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            _asyncOperation.Post(new System.Threading.SendOrPostCallback(
                delegate(object argobj)
                {
                    eventDelegate.DynamicInvoke(argobj as object[]);
                }), args);
        }
    }
}


0 commentaires