114 votes

Appels de fonction retardés

Existe-t-il une méthode simple et agréable pour retarder l'appel d'une fonction tout en laissant le fil d'exécution se poursuivre ?

par exemple

public void foo()
{
    // Do stuff!

    // Delayed call to bar() after x number of ms

    // Do more Stuff
}

public void bar()
{
    // Only execute once foo has finished
}

Je sais que cela peut être réalisé en utilisant un timer et des gestionnaires d'événements, mais je me demandais s'il existait un moyen standard en c# pour y parvenir.

Si quelqu'un est curieux, la raison pour laquelle cela est nécessaire est que foo() et bar() sont dans des classes différentes (singleton) qui peuvent avoir besoin de s'appeler l'une l'autre dans des circonstances exceptionnelles. Le problème est que cela se fait lors de l'initialisation, donc foo doit appeler bar qui a besoin d'une instance de la classe foo qui est en train d'être créée... d'où l'appel retardé à bar() pour s'assurer que foo est complètement instancié... En relisant cela, on a presque l'impression d'une mauvaise conception !

EDIT

Je prends en compte les remarques sur la mauvaise conception ! Je pense depuis longtemps que je pourrais améliorer le système, mais cette situation désagréable seulement se produit lorsqu'une exception est levée, à tout autre moment les deux singletons coexistent très bien. Je pense que je ne vais pas m'amuser avec de méchants modèles asynchrones, je vais plutôt remanier l'initialisation d'une des classes.

254voto

Korayem Points 2665

Grâce à la version moderne de C# 5/6 :)

public void foo()
{
    Task.Delay(1000).ContinueWith(t=> bar());
}

public void bar()
{
    // do stuff
}

104voto

dodgy_coder Points 2778

J'ai moi-même cherché quelque chose de ce genre - j'ai trouvé ce qui suit, bien qu'il utilise une minuterie, il ne l'utilise qu'une seule fois pour le délai initial, et ne nécessite aucune Sleep appels ...

public void foo()
{
    System.Threading.Timer timer = null; 
    timer = new System.Threading.Timer((obj) =>
                    {
                        bar();
                        timer.Dispose();
                    }, 
                null, 1000, System.Threading.Timeout.Infinite);
}

public void bar()
{
    // do stuff
}

(merci à Fred Deschenes pour l'idée de disposer du timer dans le callback)

17voto

cod3monk3y Points 1109

Outre le fait que je suis d'accord avec les observations des commentateurs précédents en matière de conception, aucune des solutions n'était suffisamment propre à mes yeux. .Net 4 fournit Dispatcher y Task les classes qui permettent de retarder l'exécution sur le fil de discussion en cours assez simple :

static class AsyncUtils
{
    static public void DelayCall(int msec, Action fn)
    {
        // Grab the dispatcher from the current executing thread
        Dispatcher d = Dispatcher.CurrentDispatcher;

        // Tasks execute in a thread pool thread
        new Task (() => {
            System.Threading.Thread.Sleep (msec);   // delay

            // use the dispatcher to asynchronously invoke the action 
            // back on the original thread
            d.BeginInvoke (fn);                     
        }).Start ();
    }
}

Pour le contexte, je l'utilise pour débiter un ICommand lié à un bouton gauche de la souris sur un élément de l'interface utilisateur. Les utilisateurs double-cliquent, ce qui provoque toutes sortes de dégâts. (Je sais que je pourrais aussi utiliser Click / DoubleClick mais je voulais une solution qui fonctionne avec les gestionnaires de ICommand dans tous les domaines).

public void Execute(object parameter)
{
    if (!IsDebouncing) {
        IsDebouncing = true;
        AsyncUtils.DelayCall (DebouncePeriodMsec, () => {
            IsDebouncing = false;
        });

        _execute ();
    }
}

7voto

Adam Ralph Points 15420

Il semble que le contrôle de la création de ces deux objets et de leur interdépendance doive se faire de manière externe, plutôt qu'entre les classes elles-mêmes.

7voto

Anton Gogolev Points 59794

C'est en effet une très mauvaise conception, sans parler du fait que le singleton en lui-même est une mauvaise conception.

Toutefois, si vous devez vraiment retarder l'exécution, voici ce que vous pouvez faire :

BackgroundWorker barInvoker = new BackgroundWorker();
barInvoker.DoWork += delegate
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        bar();
    };
barInvoker.RunWorkerAsync();

Toutefois, cette opération invoquera bar() dans un autre fil de discussion. Si vous devez appeler bar() dans le fil original que vous devriez peut-être déplacer bar() à l'invocation de RunWorkerCompleted ou de faire un peu de bricolage avec SynchronizationContext .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X