如何从WPF gui运行异步任务并与之交互

时间:2021-12-12 02:31:29

I have a WPF GUI, where I want to press a button to start a long task without freezing the window for the duration of the task. While the task is running I would like to get reports on progress, and I would like to incorporate another button that will stop the task at any time I choose.

我有一个WPF GUI,在这里我想按一个按钮来启动一个长任务,而不会在任务期间冻结窗口。当任务正在运行时,我想获得有关进度的报告,我想在我选择的任何时候添加另一个按钮来停止任务。

I cannot figure the correct way to use async/await/task. I can't include everything I've tried, but this is what I have at the moment.

我无法想出使用async / await / task的正确方法。我不能包括我尝试过的所有东西,但这就是我现在拥有的东西。

A WPF window class :

一个WPF窗口类:

public partial class MainWindow : Window
{
    readonly otherClass _burnBabyBurn = new OtherClass();
    internal bool StopWorking = false;

    //A button method to start the long running method
    private async void Button_Click_3(object sender, RoutedEventArgs e)
    {   
        Task burnTheBaby = _burnBabyBurn.ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3);

        await burnTheBaby;
    }

    //A button Method to interrupt and stop the long running method
    private void StopButton_Click(object sender, RoutedEventArgs e)
    {
        StopWorking = true;
    }

    //A method to allow the worker method to call back and update the gui
    internal void UpdateWindow(string message)
    {
        TextBox1.Text = message;
    }
}

And a class for the worker method:

还有一个worker方法的类:

class OtherClass
{
    internal Task ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
    {       
        var tcs = new TaskCompletionSource<int>();       

        //Start doing work
        gui.UpdateWindow("Work Started");        

        While(stillWorking)
        {
        //Mid procedure progress report
        gui.UpdateWindow("Bath water n% thrown out");        
        if (gui.StopTraining) return tcs.Task;
        }

        //Exit message
        gui.UpdateWindow("Done and Done");       
        return tcs.Task;        
    }
}

This runs, but the WPF function window is still blocked once the worker method starts.

这会运行,但是一旦worker方法启动,WPF函数窗口仍会被阻止。

I need to know how to arrange the async/await/task declarations to allow

我需要知道如何安排async / await / task声明来允许

A) the worker method to not block the gui window
B) let the worker method update the gui window
C) allow the gui window to stop interrupt and stop the worker method

A)不阻止gui窗口的worker方法B)让worker方法更新gui窗口C)允许gui窗口停止中断并停止worker方法

Any help or pointers are much appreciated.

任何帮助或指针都非常感谢。

5 个解决方案

#1


30  

Quick hint:

快速提示:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    txt.Text = "started";
    await Task.Run(()=> HeavyMethod(this));
    txt.Text = "done";
}
internal void HeavyMethod(MainWindow gui)
{
    while (stillWorking)
    {
        UpdateGUIMethod(gui, ".");
        System.Threading.Thread.Sleep(51);
    }
}
void UpdateGUIMethod(MainWindow gui, string text)
{
    gui.Dispatcher.Invoke(() =>
    {
        txt.Text += text;
    });
} 

Explanation:

  1. async and await must be used on the same method.

    必须在同一方法上使用async和await。

  2. Task.Run queues a method (as a Task or Task<T>) in the thread pool (it uses/creates another thread to run the task)

    Task.Run在线程池中排队方法(作为任务或任务 )(它使用/创建另一个线程来运行任务)

  3. The execution waits at await for the task to finish and throws back its results, without blocking the main thread because of the async keyword's magic ability.

    执行等待等待任务完成并抛出其结果,而不会因为async关键字的魔法能力而阻塞主线程。

  4. The magic of async keyword is that it does not create another thread. It only enables the compiler to give up and take back the control over that method.

    异步关键字的神奇之处在于它不会创建另一个线程。它只允许编译器放弃并收回对该方法的控制。

So

所以

Your main thread calls the async method (Button_Click_3) like a normal method and no threading so far... Now you can run a task inside the Button_Click_3 like this:

您的主线程调用异步方法(Button_Click_3)就像普通方法一样,到目前为止还没有线程...现在您可以在Button_Click_3中运行任务,如下所示:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    //queue a task to run on threadpool
    Task task = Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
    //wait for it to end without blocking the main thread
    await task;
}

or simply

或简单地说

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    await Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}

or if ExecuteLongProcedureAsync has a return value of type string

或者,如果ExecuteLongProcedureAsync的返回值为string类型

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    Task<string> task = Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
    string returnValue = await task;
}

or simply

或简单地说

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    string returnValue = await Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));

    //or in cases where you already have a "Task returning" method:
    //  var httpResponseInfo = await httpRequestInfo.GetResponseAsync();
}

The method inside the task (or ExecuteLongProcedureAsync) runs asynchronously and looks like this:

任务内部的方法(或ExecuteLongProcedureAsync)以异步方式运行,如下所示:

//change the value for the following flag to terminate the loop
bool stillWorking = true;

//calling this method blocks the calling thread
//you must run a task for it
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
    //Start doing work
    gui.UpdateWindow("Work Started");

    while (stillWorking)
    {
        //put a dot in the window showing the progress
        gui.UpdateWindow(".");
        //the following line will block main thread unless
        //ExecuteLongProcedureAsync is called with await keyword
        System.Threading.Thread.Sleep(51);
    }

    gui.UpdateWindow("Done and Done");
} 

Note 1:

Task.Run is newer (.NetFX4.5) and simpler version of Task.Factory.StartNew

Task.Run是较新的(.NetFX4.5)和更简单的Task.Factory.StartNew版本

await is not Task.Wait()

await不是Task.Wait()

Note 2:

Sleep blocks the main thread even that it is called in a method with async keyword.

即使在使用async关键字的方法中调用它,Sleep也会阻塞主线程。

await prevents the task from blocking the main thread because of the async keyword.

await阻止任务因async关键字而阻塞主线程。

private async void Button_Click(object sender, RoutedEventArgs e)
{
        ExecuteLongProcedureAsync();//blocks
        await Task.Run(() => ExecuteLongProcedureAsync());//does not block
}

Note 3 (GUI):

If you have to access GUI asynchronously (inside ExecuteLongProcedureAsync method), invoke any operation which involves accessing to GUI fields:

如果必须异步访问GUI(在ExecuteLongProcedureAsync方法内),请调用涉及访问GUI字段的任何操作:

void UpdateWindow(string text)
{
    //safe call
    Dispatcher.Invoke(() =>
    {
        txt.Text += text;
    });
}

However, If a task is started as a result of a property changed callback from the ViewModel, there is no need to use Dispatcher.Invoke because the callback is actually executed from the UI thread.

但是,如果由于属性从ViewModel更改回调而启动任务,则无需使用Dispatcher.Invoke,因为回调实际上是从UI线程执行的。

Accessing collections on non-UI Threads

在非UI线程*问集合

WPF enables you to access and modify data collections on threads other than the one that created the collection. This enables you to use a background thread to receive data from an external source, such as a database, and display the data on the UI thread. By using another thread to modify the collection, your user interface remains responsive to user interaction.

WPF使您可以访问和修改除创建集合之外的线程上的数据集合。这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据。通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互。

Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.

由INotifyPropertyChanged触发的值更改会自动编组回调度程序。

How to enable cross-thread access

如何启用跨线程访问

Remember, async method itself runs on the main thread. So this is valid:

请记住,异步方法本身在主线程上运行。所以这是有效的:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    txt.Text = "starting";
    await Task.Run(()=> ExecuteLongProcedureAsync1());
    txt.Text = "waiting";
    await Task.Run(()=> ExecuteLongProcedureAsync2());
    txt.Text = "finished";
}

Read more

MSDN explains Task

MSDN解释了Task

MSDN explains async

MSDN解释了异步

async await - Behind the scenes

异步等待 - 在幕后

async await - FAQ

async await - 常见问题解答

You may also read a simple asynchronous file writer to know where you should concurrent.

您还可以阅读一个简单的异步文件编写器,以了解应该并发的位置。

Investigate concurrent namespace

调查并发命名空间

Finally read this e-book: Patterns_of_Parallel_Programming_CSharp

最后阅读这本电子书:Patterns_of_Parallel_Programming_CSharp

#2


6  

Your use of TaskCompletionSource<T> is incorrect. TaskCompletionSource<T> is a way to create TAP-compatible wrappers for asynchronous operations. In your ExecuteLongProcedureAsync method, the sample code is all CPU-bound (i.e., inherently synchronous, not asynchronous).

您对TaskCompletionSource 的使用不正确。 TaskCompletionSource 是一种为异步操作创建TAP兼容包装器的方法。在ExecuteLongProcedureAsync方法中,示例代码全部受CPU限制(即,本质上是同步的,而不是异步的)。

So, it's much more natural to write ExecuteLongProcedure as a synchronous method. It's also a good idea to use standard types for standard behaviors, in particular using IProgress<T> for progress updates and CancellationToken for cancellation:

因此,将ExecuteLongProcedure编写为同步方法更为自然。对标准行为使用标准类型也是一个好主意,特别是使用IProgress 进行更新,使用CancellationToken进行取消:

internal void ExecuteLongProcedure(int param1, int param2, int param3,
    CancellationToken cancellationToken, IProgress<string> progress)
{       
  //Start doing work
  if (progress != null)
    progress.Report("Work Started");

  while (true)
  {
    //Mid procedure progress report
    if (progress != null)
      progress.Report("Bath water n% thrown out");
    cancellationToken.ThrowIfCancellationRequested();
  }

  //Exit message
  if (progress != null)
    progress.Report("Done and Done");
}

Now you have a more reusable type (no GUI dependencies) that uses the appropriate conventions. It can be used as such:

现在,您有一个使用适当约定的更可重用的类型(没有GUI依赖项)。它可以这样使用:

public partial class MainWindow : Window
{
  readonly otherClass _burnBabyBurn = new OtherClass();
  CancellationTokenSource _stopWorkingCts = new CancellationTokenSource();

  //A button method to start the long running method
  private async void Button_Click_3(object sender, RoutedEventArgs e)
  {
    var progress = new Progress<string>(data => UpdateWindow(data));
    try
    {
      await Task.Run(() => _burnBabyBurn.ExecuteLongProcedure(intParam1, intParam2, intParam3,
          _stopWorkingCts.Token, progress));
    }
    catch (OperationCanceledException)
    {
      // TODO: update the GUI to indicate the method was canceled.
    }
  }

  //A button Method to interrupt and stop the long running method
  private void StopButton_Click(object sender, RoutedEventArgs e)
  {
    _stopWorkingCts.Cancel();
  }

  //A method to allow the worker method to call back and update the gui
  void UpdateWindow(string message)
  {
    TextBox1.Text = message;
  }
}

#3


4  

This is a simplified version of the most popular answer here by Bijan. I simplified Bijan's answer to help me think through the problem using the nice formatting provided by Stack Overflow.

这是Bijan最受欢迎的答案的简化版本。我简化了Bijan的答案,帮助我使用Stack Overflow提供的漂亮格式来解决问题。

By carefully reading and editing Bijan's post I finally understood: How to wait for async method to complete?

通过仔细阅读和编辑Bijan的帖子我终于明白了:如何等待异步方法完成?

In my case the chosen answer for that other post is what ultimately led me to solve my problem:

在我的情况下,另一篇文章的选择答案最终导致我解决了我的问题:

"Avoid async void. Have your methods return Task instead of void. Then you can await them."

“避免异步void。让你的方法返回Task而不是void。然后你可以等待它们。”

My simplified version of Bijan's (excellent) answer follows:

我的简化版Bijan(优秀)答案如下:

1) This starts a task using async and await:

1)这将使用async和await启动任务:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    // if ExecuteLongProcedureAsync has a return value
    var returnValue = await Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}

2) This is the method to execute asynchronously:

2)这是异步执行的方法:

bool stillWorking = true;
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
    //Start doing work
    gui.UpdateWindow("Work Started");

    while (stillWorking)
    {
        //put a dot in the window showing the progress
        gui.UpdateWindow(".");

        //the following line blocks main thread unless
        //ExecuteLongProcedureAsync is called with await keyword
        System.Threading.Thread.Sleep(50);
    }

    gui.UpdateWindow("Done and Done");
} 

3) Invoke the operation which involves a property from gui:

3)调用涉及gui属性的操作:

void UpdateWindow(string text)
{
    //safe call
    Dispatcher.Invoke(() =>
    {
        txt.Text += text;
    });
}

Or,

要么,

void UpdateWindow(string text)
{
    //simply
    txt.Text += text;
}

Closing comments) In most cases you have two methods.

结束评论)在大多数情况下,您有两种方法。

  • First method (Button_Click_3) calls the second method and has the async modifier which tells the compiler to enable threading for that method.

    第一种方法(Button_Click_3)调用第二种方法,并具有async修饰符,告诉编译器为该方法启用线程。

    • Thread.Sleep in an async method blocks the main thread. but awaiting a task does not.
    • 异步方法中的Thread.Sleep阻塞主线程。但等待任务却没有。
    • Execution stops on current thread (second thread) on await statements until task is finished.
    • 执行在await语句的当前线程(第二个线程)上停止,直到任务完成。
    • You can't use await outside an async method
    • 您不能在异步方法之外使用等待
  • Second method (ExecuteLongProcedureAsync) is wrapped within a task and returns a generic Task<original return type> object which can be instructed to be processed asynchronously by adding await before it.

    第二个方法(ExecuteLongProcedureAsync)包装在一个任务中,并返回一个通用的Task 对象,可以通过在它之前添加await来指示异步处理。

    • Everything in this method in executed asynchronously
    • 此方法中的所有内容都是异步执行的

Important:

Liero brought up an important issue. When you are Binding an element to a ViewModel property, the property changed callback is executed in UI thread. So there is no need to use Dispatcher.Invoke. Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.

列罗提出了一个重要问题。将元素绑定到ViewModel属性时,将在UI线程中执行属性更改的回调。所以不需要使用Dispatcher.Invoke。由INotifyPropertyChanged触发的值更改会自动编组回调度程序。

#4


2  

Here is an example using async/await, IProgress<T> and CancellationTokenSource. These are the modern C# and .Net Framework language features that you should be using. The other solutions are making my eyes bleed a bit.

以下是使用async / await,IProgress 和CancellationTokenSource的示例。这些是您应该使用的现代C#和.Net Framework语言功能。其他解决方案让我的眼睛有点流血。

Code Features

  • Count to 100 over a period of 10 seconds
  • 在10秒的时间内计数到100
  • Display progress on a progress bar
  • 显示进度条上的进度
  • Long running work (a 'wait' period) performed without blocking the UI
  • 在不阻止UI的情况下执行长时间运行的工作(“等待”期间)
  • User triggered cancellation
  • 用户触发取消
  • Incremental progress updates
  • 增量进度更新
  • Post operation status report
  • 发布操作状态报告

The view

<Window x:Class="ProgressExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent="WidthAndHeight" Height="93.258" Width="316.945">
    <StackPanel>
        <Button x:Name="Button_Start" Click="Button_Click">Start</Button>
        <ProgressBar x:Name="ProgressBar_Progress" Height="20"  Maximum="100"/>
        <Button x:Name="Button_Cancel" IsEnabled="False" Click="Button_Cancel_Click">Cancel</Button>
    </StackPanel>
</Window>

The code

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private CancellationTokenSource currentCancellationSource;

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // Enable/disabled buttons so that only one counting task runs at a time.
            this.Button_Start.IsEnabled = false;
            this.Button_Cancel.IsEnabled = true;

            try
            {
                // Set up the progress event handler - this instance automatically invokes to the UI for UI updates
                // this.ProgressBar_Progress is the progress bar control
                IProgress<int> progress = new Progress<int>(count => this.ProgressBar_Progress.Value = count);

                currentCancellationSource = new CancellationTokenSource();
                await CountToOneHundredAsync(progress, this.currentCancellationSource.Token);

                // Operation was successful. Let the user know!
                MessageBox.Show("Done counting!");
            }
            catch (OperationCanceledException)
            {
                // Operation was cancelled. Let the user know!
                MessageBox.Show("Operation cancelled.");
            }
            finally
            {
                // Reset controls in a finally block so that they ALWAYS go 
                // back to the correct state once the counting ends, 
                // regardless of any exceptions
                this.Button_Start.IsEnabled = true;
                this.Button_Cancel.IsEnabled = false;
                this.ProgressBar_Progress.Value = 0;

                // Dispose of the cancellation source as it is no longer needed
                this.currentCancellationSource.Dispose();
                this.currentCancellationSource = null;
            }
        }

        private async Task CountToOneHundredAsync(IProgress<int> progress, CancellationToken cancellationToken)
        {
            for (int i = 1; i <= 100; i++)
            {
                // This is where the 'work' is performed. 
                // Feel free to swap out Task.Delay for your own Task-returning code! 
                // You can even await many tasks here

                // ConfigureAwait(false) tells the task that we dont need to come back to the UI after awaiting
                // This is a good read on the subject - https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
                await Task.Delay(100, cancellationToken).ConfigureAwait(false);

                // If cancelled, an exception will be thrown by the call the task.Delay
                // and will bubble up to the calling method because we used await!

                // Report progress with the current number
                progress.Report(i);
            }
        }

        private void Button_Cancel_Click(object sender, RoutedEventArgs e)
        {
            // Cancel the cancellation token
            this.currentCancellationSource.Cancel();
        }
    }

#5


-2  

It has been several years since the question was asked but I think it is worth noting that the BackgroundWorker class is designed precisely to achieve A, B and C requirements.

问题被问到已经有好几年了,但我认为值得注意的是,BackgroundWorker类的设计正是为了达到A,B和C的要求。

Complete sample in msdn reference page: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx

msdn参考页面中的完整示例:https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx

#1


30  

Quick hint:

快速提示:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    txt.Text = "started";
    await Task.Run(()=> HeavyMethod(this));
    txt.Text = "done";
}
internal void HeavyMethod(MainWindow gui)
{
    while (stillWorking)
    {
        UpdateGUIMethod(gui, ".");
        System.Threading.Thread.Sleep(51);
    }
}
void UpdateGUIMethod(MainWindow gui, string text)
{
    gui.Dispatcher.Invoke(() =>
    {
        txt.Text += text;
    });
} 

Explanation:

  1. async and await must be used on the same method.

    必须在同一方法上使用async和await。

  2. Task.Run queues a method (as a Task or Task<T>) in the thread pool (it uses/creates another thread to run the task)

    Task.Run在线程池中排队方法(作为任务或任务 )(它使用/创建另一个线程来运行任务)

  3. The execution waits at await for the task to finish and throws back its results, without blocking the main thread because of the async keyword's magic ability.

    执行等待等待任务完成并抛出其结果,而不会因为async关键字的魔法能力而阻塞主线程。

  4. The magic of async keyword is that it does not create another thread. It only enables the compiler to give up and take back the control over that method.

    异步关键字的神奇之处在于它不会创建另一个线程。它只允许编译器放弃并收回对该方法的控制。

So

所以

Your main thread calls the async method (Button_Click_3) like a normal method and no threading so far... Now you can run a task inside the Button_Click_3 like this:

您的主线程调用异步方法(Button_Click_3)就像普通方法一样,到目前为止还没有线程...现在您可以在Button_Click_3中运行任务,如下所示:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    //queue a task to run on threadpool
    Task task = Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
    //wait for it to end without blocking the main thread
    await task;
}

or simply

或简单地说

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    await Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}

or if ExecuteLongProcedureAsync has a return value of type string

或者,如果ExecuteLongProcedureAsync的返回值为string类型

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    Task<string> task = Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
    string returnValue = await task;
}

or simply

或简单地说

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    string returnValue = await Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));

    //or in cases where you already have a "Task returning" method:
    //  var httpResponseInfo = await httpRequestInfo.GetResponseAsync();
}

The method inside the task (or ExecuteLongProcedureAsync) runs asynchronously and looks like this:

任务内部的方法(或ExecuteLongProcedureAsync)以异步方式运行,如下所示:

//change the value for the following flag to terminate the loop
bool stillWorking = true;

//calling this method blocks the calling thread
//you must run a task for it
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
    //Start doing work
    gui.UpdateWindow("Work Started");

    while (stillWorking)
    {
        //put a dot in the window showing the progress
        gui.UpdateWindow(".");
        //the following line will block main thread unless
        //ExecuteLongProcedureAsync is called with await keyword
        System.Threading.Thread.Sleep(51);
    }

    gui.UpdateWindow("Done and Done");
} 

Note 1:

Task.Run is newer (.NetFX4.5) and simpler version of Task.Factory.StartNew

Task.Run是较新的(.NetFX4.5)和更简单的Task.Factory.StartNew版本

await is not Task.Wait()

await不是Task.Wait()

Note 2:

Sleep blocks the main thread even that it is called in a method with async keyword.

即使在使用async关键字的方法中调用它,Sleep也会阻塞主线程。

await prevents the task from blocking the main thread because of the async keyword.

await阻止任务因async关键字而阻塞主线程。

private async void Button_Click(object sender, RoutedEventArgs e)
{
        ExecuteLongProcedureAsync();//blocks
        await Task.Run(() => ExecuteLongProcedureAsync());//does not block
}

Note 3 (GUI):

If you have to access GUI asynchronously (inside ExecuteLongProcedureAsync method), invoke any operation which involves accessing to GUI fields:

如果必须异步访问GUI(在ExecuteLongProcedureAsync方法内),请调用涉及访问GUI字段的任何操作:

void UpdateWindow(string text)
{
    //safe call
    Dispatcher.Invoke(() =>
    {
        txt.Text += text;
    });
}

However, If a task is started as a result of a property changed callback from the ViewModel, there is no need to use Dispatcher.Invoke because the callback is actually executed from the UI thread.

但是,如果由于属性从ViewModel更改回调而启动任务,则无需使用Dispatcher.Invoke,因为回调实际上是从UI线程执行的。

Accessing collections on non-UI Threads

在非UI线程*问集合

WPF enables you to access and modify data collections on threads other than the one that created the collection. This enables you to use a background thread to receive data from an external source, such as a database, and display the data on the UI thread. By using another thread to modify the collection, your user interface remains responsive to user interaction.

WPF使您可以访问和修改除创建集合之外的线程上的数据集合。这使您可以使用后台线程从外部源(如数据库)接收数据,并在UI线程上显示数据。通过使用另一个线程来修改集合,您的用户界面仍然可以响应用户交互。

Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.

由INotifyPropertyChanged触发的值更改会自动编组回调度程序。

How to enable cross-thread access

如何启用跨线程访问

Remember, async method itself runs on the main thread. So this is valid:

请记住,异步方法本身在主线程上运行。所以这是有效的:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    txt.Text = "starting";
    await Task.Run(()=> ExecuteLongProcedureAsync1());
    txt.Text = "waiting";
    await Task.Run(()=> ExecuteLongProcedureAsync2());
    txt.Text = "finished";
}

Read more

MSDN explains Task

MSDN解释了Task

MSDN explains async

MSDN解释了异步

async await - Behind the scenes

异步等待 - 在幕后

async await - FAQ

async await - 常见问题解答

You may also read a simple asynchronous file writer to know where you should concurrent.

您还可以阅读一个简单的异步文件编写器,以了解应该并发的位置。

Investigate concurrent namespace

调查并发命名空间

Finally read this e-book: Patterns_of_Parallel_Programming_CSharp

最后阅读这本电子书:Patterns_of_Parallel_Programming_CSharp

#2


6  

Your use of TaskCompletionSource<T> is incorrect. TaskCompletionSource<T> is a way to create TAP-compatible wrappers for asynchronous operations. In your ExecuteLongProcedureAsync method, the sample code is all CPU-bound (i.e., inherently synchronous, not asynchronous).

您对TaskCompletionSource 的使用不正确。 TaskCompletionSource 是一种为异步操作创建TAP兼容包装器的方法。在ExecuteLongProcedureAsync方法中,示例代码全部受CPU限制(即,本质上是同步的,而不是异步的)。

So, it's much more natural to write ExecuteLongProcedure as a synchronous method. It's also a good idea to use standard types for standard behaviors, in particular using IProgress<T> for progress updates and CancellationToken for cancellation:

因此,将ExecuteLongProcedure编写为同步方法更为自然。对标准行为使用标准类型也是一个好主意,特别是使用IProgress 进行更新,使用CancellationToken进行取消:

internal void ExecuteLongProcedure(int param1, int param2, int param3,
    CancellationToken cancellationToken, IProgress<string> progress)
{       
  //Start doing work
  if (progress != null)
    progress.Report("Work Started");

  while (true)
  {
    //Mid procedure progress report
    if (progress != null)
      progress.Report("Bath water n% thrown out");
    cancellationToken.ThrowIfCancellationRequested();
  }

  //Exit message
  if (progress != null)
    progress.Report("Done and Done");
}

Now you have a more reusable type (no GUI dependencies) that uses the appropriate conventions. It can be used as such:

现在,您有一个使用适当约定的更可重用的类型(没有GUI依赖项)。它可以这样使用:

public partial class MainWindow : Window
{
  readonly otherClass _burnBabyBurn = new OtherClass();
  CancellationTokenSource _stopWorkingCts = new CancellationTokenSource();

  //A button method to start the long running method
  private async void Button_Click_3(object sender, RoutedEventArgs e)
  {
    var progress = new Progress<string>(data => UpdateWindow(data));
    try
    {
      await Task.Run(() => _burnBabyBurn.ExecuteLongProcedure(intParam1, intParam2, intParam3,
          _stopWorkingCts.Token, progress));
    }
    catch (OperationCanceledException)
    {
      // TODO: update the GUI to indicate the method was canceled.
    }
  }

  //A button Method to interrupt and stop the long running method
  private void StopButton_Click(object sender, RoutedEventArgs e)
  {
    _stopWorkingCts.Cancel();
  }

  //A method to allow the worker method to call back and update the gui
  void UpdateWindow(string message)
  {
    TextBox1.Text = message;
  }
}

#3


4  

This is a simplified version of the most popular answer here by Bijan. I simplified Bijan's answer to help me think through the problem using the nice formatting provided by Stack Overflow.

这是Bijan最受欢迎的答案的简化版本。我简化了Bijan的答案,帮助我使用Stack Overflow提供的漂亮格式来解决问题。

By carefully reading and editing Bijan's post I finally understood: How to wait for async method to complete?

通过仔细阅读和编辑Bijan的帖子我终于明白了:如何等待异步方法完成?

In my case the chosen answer for that other post is what ultimately led me to solve my problem:

在我的情况下,另一篇文章的选择答案最终导致我解决了我的问题:

"Avoid async void. Have your methods return Task instead of void. Then you can await them."

“避免异步void。让你的方法返回Task而不是void。然后你可以等待它们。”

My simplified version of Bijan's (excellent) answer follows:

我的简化版Bijan(优秀)答案如下:

1) This starts a task using async and await:

1)这将使用async和await启动任务:

private async void Button_Click_3(object sender, RoutedEventArgs e)
{
    // if ExecuteLongProcedureAsync has a return value
    var returnValue = await Task.Run(()=>
        ExecuteLongProcedureAsync(this, intParam1, intParam2, intParam3));
}

2) This is the method to execute asynchronously:

2)这是异步执行的方法:

bool stillWorking = true;
internal void ExecuteLongProcedureAsync(MainWindow gui, int param1, int param2, int param3)
{
    //Start doing work
    gui.UpdateWindow("Work Started");

    while (stillWorking)
    {
        //put a dot in the window showing the progress
        gui.UpdateWindow(".");

        //the following line blocks main thread unless
        //ExecuteLongProcedureAsync is called with await keyword
        System.Threading.Thread.Sleep(50);
    }

    gui.UpdateWindow("Done and Done");
} 

3) Invoke the operation which involves a property from gui:

3)调用涉及gui属性的操作:

void UpdateWindow(string text)
{
    //safe call
    Dispatcher.Invoke(() =>
    {
        txt.Text += text;
    });
}

Or,

要么,

void UpdateWindow(string text)
{
    //simply
    txt.Text += text;
}

Closing comments) In most cases you have two methods.

结束评论)在大多数情况下,您有两种方法。

  • First method (Button_Click_3) calls the second method and has the async modifier which tells the compiler to enable threading for that method.

    第一种方法(Button_Click_3)调用第二种方法,并具有async修饰符,告诉编译器为该方法启用线程。

    • Thread.Sleep in an async method blocks the main thread. but awaiting a task does not.
    • 异步方法中的Thread.Sleep阻塞主线程。但等待任务却没有。
    • Execution stops on current thread (second thread) on await statements until task is finished.
    • 执行在await语句的当前线程(第二个线程)上停止,直到任务完成。
    • You can't use await outside an async method
    • 您不能在异步方法之外使用等待
  • Second method (ExecuteLongProcedureAsync) is wrapped within a task and returns a generic Task<original return type> object which can be instructed to be processed asynchronously by adding await before it.

    第二个方法(ExecuteLongProcedureAsync)包装在一个任务中,并返回一个通用的Task 对象,可以通过在它之前添加await来指示异步处理。

    • Everything in this method in executed asynchronously
    • 此方法中的所有内容都是异步执行的

Important:

Liero brought up an important issue. When you are Binding an element to a ViewModel property, the property changed callback is executed in UI thread. So there is no need to use Dispatcher.Invoke. Value changes fired by INotifyPropertyChanged are automatically marshalled back onto the dispatcher.

列罗提出了一个重要问题。将元素绑定到ViewModel属性时,将在UI线程中执行属性更改的回调。所以不需要使用Dispatcher.Invoke。由INotifyPropertyChanged触发的值更改会自动编组回调度程序。

#4


2  

Here is an example using async/await, IProgress<T> and CancellationTokenSource. These are the modern C# and .Net Framework language features that you should be using. The other solutions are making my eyes bleed a bit.

以下是使用async / await,IProgress 和CancellationTokenSource的示例。这些是您应该使用的现代C#和.Net Framework语言功能。其他解决方案让我的眼睛有点流血。

Code Features

  • Count to 100 over a period of 10 seconds
  • 在10秒的时间内计数到100
  • Display progress on a progress bar
  • 显示进度条上的进度
  • Long running work (a 'wait' period) performed without blocking the UI
  • 在不阻止UI的情况下执行长时间运行的工作(“等待”期间)
  • User triggered cancellation
  • 用户触发取消
  • Incremental progress updates
  • 增量进度更新
  • Post operation status report
  • 发布操作状态报告

The view

<Window x:Class="ProgressExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent="WidthAndHeight" Height="93.258" Width="316.945">
    <StackPanel>
        <Button x:Name="Button_Start" Click="Button_Click">Start</Button>
        <ProgressBar x:Name="ProgressBar_Progress" Height="20"  Maximum="100"/>
        <Button x:Name="Button_Cancel" IsEnabled="False" Click="Button_Cancel_Click">Cancel</Button>
    </StackPanel>
</Window>

The code

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private CancellationTokenSource currentCancellationSource;

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            // Enable/disabled buttons so that only one counting task runs at a time.
            this.Button_Start.IsEnabled = false;
            this.Button_Cancel.IsEnabled = true;

            try
            {
                // Set up the progress event handler - this instance automatically invokes to the UI for UI updates
                // this.ProgressBar_Progress is the progress bar control
                IProgress<int> progress = new Progress<int>(count => this.ProgressBar_Progress.Value = count);

                currentCancellationSource = new CancellationTokenSource();
                await CountToOneHundredAsync(progress, this.currentCancellationSource.Token);

                // Operation was successful. Let the user know!
                MessageBox.Show("Done counting!");
            }
            catch (OperationCanceledException)
            {
                // Operation was cancelled. Let the user know!
                MessageBox.Show("Operation cancelled.");
            }
            finally
            {
                // Reset controls in a finally block so that they ALWAYS go 
                // back to the correct state once the counting ends, 
                // regardless of any exceptions
                this.Button_Start.IsEnabled = true;
                this.Button_Cancel.IsEnabled = false;
                this.ProgressBar_Progress.Value = 0;

                // Dispose of the cancellation source as it is no longer needed
                this.currentCancellationSource.Dispose();
                this.currentCancellationSource = null;
            }
        }

        private async Task CountToOneHundredAsync(IProgress<int> progress, CancellationToken cancellationToken)
        {
            for (int i = 1; i <= 100; i++)
            {
                // This is where the 'work' is performed. 
                // Feel free to swap out Task.Delay for your own Task-returning code! 
                // You can even await many tasks here

                // ConfigureAwait(false) tells the task that we dont need to come back to the UI after awaiting
                // This is a good read on the subject - https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
                await Task.Delay(100, cancellationToken).ConfigureAwait(false);

                // If cancelled, an exception will be thrown by the call the task.Delay
                // and will bubble up to the calling method because we used await!

                // Report progress with the current number
                progress.Report(i);
            }
        }

        private void Button_Cancel_Click(object sender, RoutedEventArgs e)
        {
            // Cancel the cancellation token
            this.currentCancellationSource.Cancel();
        }
    }

#5


-2  

It has been several years since the question was asked but I think it is worth noting that the BackgroundWorker class is designed precisely to achieve A, B and C requirements.

问题被问到已经有好几年了,但我认为值得注意的是,BackgroundWorker类的设计正是为了达到A,B和C的要求。

Complete sample in msdn reference page: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx

msdn参考页面中的完整示例:https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx