c# 高级编程 15章323页 【异步与Windows应用程序】

251 阅读2分钟

异步与Windows应用程序

UWP应用程序中,在UI线程中调用await之后,当异步方法返回时,将默认返回到UI线程中。这允许直接访问UI元素。

UWP应用程序,定义了一个同步上下文

private async void OnStartAsync(object sender, RoutedEventArgs e)
{
    text1.Text = $"UI thread: {GetThread()}";
    await Task.Delay(1000);
    text1.Text += $"\n after await: {GetThread()}";
}
//UWP应用程序中,Thread类是无效的,可以用Environment类
private string GetThread() => $"thread: {Environment.CurrentManagedThreadId}";

输出:

UI thread: thread 3
UI thread: thread 3

如何不使用同步上下文

  • ConfigureAwait(continueOnCapturedContext: true) 就可以不使用同步上下文
private async void OnStartAsyncConfigureAwait(object sender, RountedEventArgs e)
{
    text1.Text = $"UI thread: {GetThread()}";
    
    string s = await AsyncFunction().ConfigureAwait(continueOnCapturedContext: true);
    // after await, with continueOnCapturedContext true, we are back in the UI thread
    text1.Text += $"\n{s}\nafter await: {GetThread()}";
    
    async Task<string> AsyncFunction()
    {
        string result = $"\nasync function: {GetThread()}";
        await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false);
        result += $"\nasync function after await : {GetThread()}";
        
        try
        {
            text1.Text = "this is a call from the wrong thread";
            return "not reached";
        }
        catch (Exception ex) when (ex.HResult == -2147417842)
        {
            return result;
            //we know it's the wrong thread
            //don't access UI elements from the previous try block
        }
    }
}

输出:

UI thread: thread 3
async function: thread 3
async function after await: thread 6
after await : thread 3

Dispatcher

有时候,在后台线程里访问 UI 元素,不容易。可以用Dispatcher

  • UI元素的基类DependencyObject类里,有个Dispatcher属性
  • Dispatcher属性,所返回的CoreDispatcher对象,可以切换到UI线程
  • CoreDispatcher对象的RunAsync方法,会在UI线程中再次运行传递进来的lamda表达式
private async void OnStartAsyncConfigureAwait(object sender, RountedEventArgs e)
{
    text1.Text = $"UI thread: {GetThread()}";
    
    string s = await AsyncFunction();

    text1.Text += $"\n{s}\nafter await: {GetThread()}";
    
    async Task<string> AsyncFunction()
    {
        string result = $"\nasync function: {GetThread()}";
        await Task.Delay(1000).ConfigureAwait(continueOnCapturedContext: false);
        result += $"\nasync function after await : {GetThread()}";
        
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 
        {
            //因为回到了UI线程,UI线程没被阻塞,所以这一句反而可以执行得早
            text1.Text = $"\nasync function switch back to the UI thread: {GetThread()}";
        });
        return "not reached";        
    }
}

输出:

UI thread: thread 3
async function switch back to the UI thread: thread 3
async function: thread 3
async function after await: thread 6
after await : thread 3

IAsyncOperation

Windows运行库定义的那些异步方法,不返回TaskValueTask。而是返回一个IAsyncOperation对象。

  • 那些异步方法,例如:MessageDialog的ShowAsync()方法
  • 我们依然可以await Windows运行库定义的那些异步方法
    • 因为,当使用await关键字时,IAsyncOperation会自动转换为Task
    • 而且,我们还可以显式调用AsTask扩展方法,将IAsyncOperation转换为Task

避免死锁

Task上,一起使用Waitasync是很危险的在使用同步上下文时,这很容易导致死锁。

private void OnStartDeadlock(object sender, RouteEventArgs e)
{
    //Wait()方法,阻塞调用线程,直到任务完成
    DelayAsync().Wait();
}

private async Task DelayAsync()
{
    //Windows应用程序是使用同步上下文的,因此下面这句await执行完,会回到调用线程
    //然而调用线程被Wait()阻塞,不可用
    //因此,产生了死锁
    await Task.Delay(1000);
}

应该避免,在使用同步上下文的应用程序中,同时使用Wait和await

UWP应用程序

  • 需要打开“开发人员模式”
    • 这样就可以运行不是从Windows Store中安装的应用程序
    • 并为“开发人员模式”添加一个Windows包

image.png