4 votes

Comment invoquer une méthode sur le thread de l'interface utilisateur lors de l'utilisation de la TPL ?

Je travaille sur une application MVVM qui exécute plusieurs tâches en arrière-plan, en utilisant TPL. Les tâches doivent signaler leur progression à l'interface utilisateur afin qu'une boîte de dialogue de progression puisse être mise à jour. Puisque l'application est MVVM, la boîte de dialogue de progression est liée à une propriété du modèle de vue nommée Progress, qui est mise à jour par une méthode du modèle de vue avec la signature UpdateProgress(int increment) . Les tâches d'arrière-plan doivent appeler cette méthode pour signaler leur progression.

J'utilise une méthode pour mettre à jour la propriété parce qu'elle permet à chaque tâche d'incrémenter la propriété Progress de différentes quantités. Ainsi, si j'ai deux tâches et que la première prend quatre fois plus de temps que la seconde, la première tâche appelle la méthode UpdateProgress(4) et la seconde tâche appelle UpdateProgress(1) . Ainsi, la progression est de 80 % lorsque la première tâche est terminée, et de 100 % lorsque la deuxième tâche est terminée.

Ma question est en fait assez simple : Comment appeler la méthode du modèle de vue à partir de mes tâches d'arrière-plan ? Le code est ci-dessous. Merci pour votre aide.


Les tâches utilisent Parallel.ForEach() dans un code qui ressemble à celui-ci :

private void ResequenceFiles(IEnumerable<string> fileList, ProgressDialogViewModel viewModel)
{
    // Wrap token source in a Parallel Options object
    var loopOptions = new ParallelOptions();
    loopOptions.CancellationToken = viewModel.TokenSource.Token;

    // Process images in parallel
    try
    {
        Parallel.ForEach(fileList, loopOptions, sourcePath =>
        {
            var fileName = Path.GetFileName(sourcePath);
            if (fileName == null) throw new ArgumentException("File list contains a bad file path.");
            var destPath = Path.Combine(m_ViewModel.DestFolder, fileName);
            SetImageTimeAttributes(sourcePath, destPath);

            // This statement isn't working
            viewModel.IncrementProgressCounter(1);
        });
    }
    catch (OperationCanceledException)
    {
        viewModel.ProgressMessage = "Image processing cancelled.";
    }
}

La déclaration viewModel.IncrementProgressCounter(1) ne lève pas d'exception, mais ne parvient pas à atteindre le fil d'exécution principal. Les tâches sont appelées depuis MVVM ICommand dans un code qui ressemble à celui-ci :

public void Execute(object parameter)
{
    ...

    // Background Task #2: Resequence files
    var secondTask = firstTask.ContinueWith(t => this.ResequenceFiles(fileList, progressDialogViewModel));

    ...
}

9voto

Pellared Points 578

En supposant que votre ViewModel soit construit sur le thread de l'interface utilisateur (c'est-à-dire par la vue, ou en réponse à un événement lié à la vue), ce qui est presque toujours le cas en IMO, vous pouvez ajouter ceci à votre constructeur :

// Add to class:
TaskFactory uiFactory;

public MyViewModel()
{
    // Construct a TaskFactory that uses the UI thread's context
    uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}

Ensuite, lorsque vous obtenez votre événement, vous pouvez l'utiliser pour l'organiser :

void Something()
{
    uiFactory.StartNew( () => DoSomething() );
}

Edita: J'ai fait une classe d'util. Elle est statique mais si vous le souhaitez, vous pouvez créer une interface pour elle et la rendre non statique :

public static class UiDispatcher
{
    private static SynchronizationContext UiContext { get; set; }

    /// <summary>
    /// This method should be called once on the UI thread to ensure that
    /// the <see cref="UiContext" /> property is initialized.
    /// <para>In a Silverlight application, call this method in the
    /// Application_Startup event handler, after the MainPage is constructed.</para>
    /// <para>In WPF, call this method on the static App() constructor.</para>
    /// </summary>
    public static void Initialize()
    {
        if (UiContext == null)
        {
            UiContext = SynchronizationContext.Current;
        }
    }

    /// <summary>
    /// Invokes an action asynchronously on the UI thread.
    /// </summary>
    /// <param name="action">The action that must be executed.</param>
    public static void InvokeAsync(Action action)
    {
        CheckInitialization();

        UiContext.Post(x => action(), null);
    }

    /// <summary>
    /// Executes an action on the UI thread. If this method is called
    /// from the UI thread, the action is executed immendiately. If the
    /// method is called from another thread, the action will be enqueued
    /// on the UI thread's dispatcher and executed asynchronously.
    /// <para>For additional operations on the UI thread, you can get a
    /// reference to the UI thread's context thanks to the property
    /// <see cref="UiContext" /></para>.
    /// </summary>
    /// <param name="action">The action that will be executed on the UI
    /// thread.</param>
    public static void Invoke(Action action)
    {
        CheckInitialization();

        if (UiContext == SynchronizationContext.Current)
        {
            action();
        }
        else
        {
            InvokeAsync(action);
        }
    }

    private static void CheckInitialization()
    {
        if (UiContext == null) throw new InvalidOperationException("UiDispatcher is not initialized. Invoke Initialize() first.");
    }
}

Utilisation :

void Something()
{
    UiDispatcher.Invoke( () => DoSomething() );
}

2voto

Hadi Eskandari Points 5344

Pour rassembler les appels de méthodes dans le thread principal de l'interface utilisateur, vous pouvez utiliser la méthode InvokeMethod du Dispatcher. Si vous utilisez des frameworks MVVM comme Carliburn, ils ont des abstractions sur Dispatcher et vous pouvez donc faire presque la même chose en utilisant Execute.OnUIThread(Action).

Vérifier este Article de Microsoft sur l'utilisation de Dispatcher.

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