Skip to content

WPF中的各种异步

约 1407 字大约 5 分钟

WPF

2025-05-09

1.正常异步

 private async void Button_Click(object sender, RoutedEventArgs e)
 {
     for (int i = 0; i < 3; i++)
     {
         Debug.WriteLine($"=={i}.==");

         await DoWorkAsync(i);

         Debug.WriteLine($"=={i}.end==");
     }
 }
 public async Task DoWorkAsync(int i)
 {
     Debug.WriteLine(i + "开始工作");
     await Task.Delay(1000); // 模拟异步操作
     Debug.WriteLine(i + "工作完成");
 }
 
/***输出:****
==0.==
0开始工作
0工作完成
==0.end==
==1.==
1开始工作
1工作完成
==1.end==
==2.==
2开始工作
2工作完成
==2.end==
*************/

按部就班执行返回,因为Task.Delay(1000)异步非阻塞,UI不会阻塞

2.定义一个不需要等待结果回来的异步 (fire-and-forget)

调用了就不管 缺点:不知道是否执行完成,无法捕获异常或者获取返回值

 private void Button_Click(object sender, RoutedEventArgs e)
 {
     for (int i = 0; i < 3; i++)
     {
         Debug.WriteLine($"=={i}.==");

         DoWorkAsync(i);

         Debug.WriteLine($"=={i}.end==");
     }
 }
 public async Task DoWorkAsync(int i)
 {
     Debug.WriteLine(i + "开始工作");
     await Task.Delay(1000); // 模拟异步操作
     Debug.WriteLine(i + "工作完成");
 }
 
/***输出:****
==0.==
0开始工作
==0.end==
==1.==
1开始工作
==1.end==
==2.==
2开始工作
==2.end==
1工作完成
2工作完成
0工作完成
*************/

3.死锁示例

private  void Button_Click(object sender, RoutedEventArgs e)
{
    for (int i = 0; i < 3; i++)
    {
        Debug.WriteLine($"=={i}.==");
        
        var r = DoWorkAsync(i);
	    var result = r.Result;
	    //或者 DoWorkAsync(i).Wait();都会造成死锁
        
        Debug.WriteLine($"=={i}.end==");
    }
}
public async Task<int> DoWorkAsync(int i)
{
    Debug.WriteLine(i + "开始工作");
    await Task.Delay(1000); // 模拟异步操作
    Debug.WriteLine(i + "工作完成");
    return i * 2;
}

解析为什么会造成死锁 因为DoWorkAsync依赖于UI线程,UI线程需要处理消息和界面,wait()Result都是同步阻塞的方法占用UI线程,会等待DoWorkAsync()执行完成后释放,DoWorkAsync的Task.Delay虽然本身不依赖UI线程,但是他await后会尝试在UI线程上继续执行,所以会造成互相等待释放,死锁! 解决:1不使用wait()Result,2让Task.Delay不依赖UI线程Task.Delay(1000).ConfigureAwait(false)

3.1 死锁解决与执行线程

直观的添加执行的线程的id

List<Task> tasks = new List<Task>();
public Action<int, int> ResultNoti = (iii, rrr) => Debug.WriteLine("通知结果 " + iii + "->" + rrr);
private async void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        for (int i = 0; i < 7; i++)
        {
            // 在 UI 线程启动
            Debug.WriteLine("UI 线程ID: " + Environment.CurrentManagedThreadId);

            // 模拟长时间运行的任务(不阻塞 UI)
            int result = await DoWorkAsync(i).ConfigureAwait(false);

            // 注意:这里不会自动回到 UI 线程!
            Debug.WriteLine("当前线程ID: " + Environment.CurrentManagedThreadId);
            Debug.WriteLine("结果: " + result);

            // 如果需要更新 UI,必须手动调度回 UI 线程
            Dispatcher.Invoke(() =>
            {
                Debug.WriteLine("Dispatcher 线程ID: " + Environment.CurrentManagedThreadId);
                this.Content = result.ToString();
            });
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("发生异常: " + ex.Message);
    }
}
public async Task<int> DoWorkAsync(int i)
{
    Debug.WriteLine(i + "开始工作");
    await Task.Delay(1000).ConfigureAwait(false); //模拟异步操作
    Debug.WriteLine(i + " 工作完成");
    int r = i * 2;
    ResultNoti.Invoke(i, r);
    return r;
}
/***
UI 线程ID: 1
0开始工作
0 工作完成
通知结果 0->0
当前线程ID: 9
结果: 0
Dispatcher 线程ID: 1
UI 线程ID: 9
1开始工作
1 工作完成
通知结果 1->2
当前线程ID: 9
结果: 2
Dispatcher 线程ID: 1
UI 线程ID: 9
2开始工作
2 工作完成
通知结果 2->4
当前线程ID: 9
结果: 4
Dispatcher 线程ID: 1
UI 线程ID: 9
3开始工作
3 工作完成
通知结果 3->6
当前线程ID: 9
结果: 6
Dispatcher 线程ID: 1
UI 线程ID: 9
4开始工作
4 工作完成
通知结果 4->8
当前线程ID: 9
结果: 8
Dispatcher 线程ID: 1
UI 线程ID: 9
5开始工作
5 工作完成
通知结果 5->10
当前线程ID: 9
结果: 10
Dispatcher 线程ID: 1
UI 线程ID: 9
6开始工作
*****/

可以看到UI线程的id为1,线程池的id为9; 为什么除了第一次的UI线程为1 后面都是9,因为在await Task.Delay(1000).ConfigureAwait(false)后 await的代码都不会回到UI线程执行了都是在线程池的线程执行所以id都是9,但是明显UI线程的id是1,通过Dispatcher.Invoke可以明显看出来

4.添加完成监听

public Action<int, int> ResultNoti= (iii, rrr) => Debug.WriteLine("通知结果 " + iii + "->" + rrr);
private void Button_Click(object sender, RoutedEventArgs e)
{
    for (int i = 0; i < 3; i++)
    {
        Debug.WriteLine($"=={i}.==");

        DoWorkAsync(i);

        Debug.WriteLine($"=={i}.end==");
    }
}
public async Task<int> DoWorkAsync(int i)
{
    Debug.WriteLine(i + "开始工作");
    await Task.Delay(1000); //模拟异步操作
    Debug.WriteLine(i + " 工作完成");
    int r = i * 2;
    ResultNoti.Invoke(i, r);
    return r;
}
/***输出:****
==0.==
0开始工作
==0.end==
==1.==
1开始工作
==1.end==
==2.==
2开始工作
==2.end==
0 工作完成
通知结果 0->0
1 工作完成
通知结果 1->2
2 工作完成
通知结果 2->4
*************/

4.1 进度报告IProgress

private async void StartProcessingButton_Click(object sender, RoutedEventArgs e)
{
    var progress = new Progress<int>(percent =>
    {
        ProgressBar.Value = percent;
        ProgressText.Text = $"{percent}%";
    });

    await ProcessDataAsync(progress);
}

public async Task ProcessDataAsync(IProgress<int> progress)
{
    for (int i = 0; i <= 100; i += 10)
    {
        await Task.Delay(300); // 模拟耗时操作
        progress?.Report(i);  // 自动回到 UI 线程更新
    }
}

Progress<T>内部使用的SynchronizationContext同步上下文,保证回调在UI线程执行

5.优化方案

public Action<int, int> ResultNoti= (iii, rrr) => Debug.WriteLine("通知结果 " + iii + "->" + rrr);
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var tasks = new List<Task<int>>();
    for (int i = 0; i < 3; i++)
    {
        Debug.WriteLine($"=={i}.==");

        tasks.Add(DoWorkAsync(i));

        Debug.WriteLine($"=={i}.end==");
    }
    int[] ints =  await Task.WhenAll(tasks);
    Debug.WriteLine("所有任务完成");
    for (int i = 0; i < ints.Length; i++)
    {
        Debug.WriteLine($"{ints[i]}");
    }

}
public async Task<int> DoWorkAsync(int i)
{
    Debug.WriteLine(i + "开始工作");
    await Task.Delay(1000); //模拟异步操作
    Debug.WriteLine(i + " 工作完成");
    int r = i * 2;
    ResultNoti.Invoke(i, r);
    return r;
}
/*******
==0.==
0开始工作
==0.end==
==1.==
1开始工作
==1.end==
==2.==
2开始工作
==2.end==
2 工作完成
通知结果 2->4
0 工作完成
通知结果 0->0
1 工作完成
通知结果 1->2
所有任务完成
0
2
4
**************/