You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用多async/await实现文件压缩复制时UI冻结且仅处理首个项的问题排查

问题根源与解决方案分析

你的代码出现UI冻结且仅处理第一个项的问题,主要有两个核心原因,下面逐一拆解并给出修复方案:

一、UI冻结的原因:同步阻塞操作占用UI线程

虽然你用了async/await,但TaskOperationBackupOffline里的大部分操作(比如归档创建、文件添加、目录操作)都是同步阻塞方法,而且这些方法默认在UI线程执行(因为你没有把它们放到后台线程),直接卡住了UI消息循环,导致界面冻结。

另外,直接在任务中调用dgwStation.Items.Refresh()属于跨线程访问UI元素,虽然async方法在await后会回到UI上下文,但前面的同步阻塞代码已经占用了UI线程,无法及时更新界面。

二、仅处理第一个项的原因:共享归档实例导致线程冲突

看起来ArchivioB1是一个全局类成员,多个并行任务同时操作同一个实例,会导致严重的线程安全问题:第一个任务打开归档后,后续任务的归档操作会被干扰(比如无法重复打开归档、覆盖操作状态),最终只有第一个任务能正常执行。


针对性修复方案

1. 将同步阻塞操作移到后台线程

把所有耗时的同步操作(归档、目录创建等)包装到Task.Run中,让它们在ThreadPool线程执行,释放UI线程。同时封装UI更新逻辑,确保在UI线程执行。

2. 为每个任务创建独立的归档实例

每个并行任务必须使用自己的ArchivioB1实例,避免共享资源导致的冲突。

3. 优化取消令牌检查与路径处理

在关键步骤前检查取消令牌,确保能及时响应用户取消;用Path.Combine替代手动拼接路径,避免分隔符错误。

修复后的代码示例

按钮点击事件(无需大改,保持原有任务调度逻辑)

var row_list = GetDataGridRows(dgwStation);
tokenSource = new CancellationTokenSource();
var tasks = new List<Task<string>>();

foreach (DataGridRow single_row in row_list)
{
    var row = single_row.GetIndex();
    tasks.Add(TaskOperationBackupOffline(row, tokenSource.Token));
}

btBackup.IsEnabled = false;
btCancel.IsEnabled = true;
await Task.WhenAll(tasks);

修改后的TaskOperationBackupOffline方法

private async Task<string> TaskOperationBackupOffline(int rowindex, CancellationToken ct)
{
    try
    {
        // 先获取当前项数据,避免后台线程直接访问UI绑定集合
        var currentItem = StationItemsList[rowindex];
        string source = currentItem.ControlPath;
        string target = currentItem.Destination;
        string filename = currentItem.StationName;

        // 更新UI:检查源目录状态
        UpdateItemState(rowindex, Chiavi.dict["backupon_controlorigin"].ToString());

        // 检查源目录是否存在(可在UI线程执行)
        if (!Directory.Exists(source))
        {
            UpdateItemState(rowindex, Chiavi.dict["backupstate_source"].ToString());
            generalerror = true;
            return "source error";
        }

        // 更新UI:检查目标目录状态
        UpdateItemState(rowindex, Chiavi.dict["backupon_controldestinationdata"].ToString());

        // 后台创建目标目录
        await Task.Run(() => 
        {
            ct.ThrowIfCancellationRequested();
            if (!Directory.Exists(target))
            {
                Directory.CreateDirectory(target);
            }
        }, ct);

        ct.ThrowIfCancellationRequested();

        // 更新UI:准备归档状态
        UpdateItemState(rowindex, Chiavi.dict["backupon_preparation"].ToString());

        // 生成归档文件名(用Path.Combine避免路径错误)
        string timestamp = $"{DateTime.Now.Year}-{voidGeneral.ConversioneDecimale(DateTime.Now.Month)}-{voidGeneral.ConversioneDecimale(DateTime.Now.Day)}_{voidGeneral.ConversioneDecimale(DateTime.Now.Hour)}_{voidGeneral.ConversioneDecimale(DateTime.Now.Minute)}_{voidGeneral.ConversioneDecimale(DateTime.Now.Second)}";
        string archivePath = Path.Combine(target, $"{timestamp}-{filename}.zip");

        ct.ThrowIfCancellationRequested();

        // 更新UI:创建ZIP状态
        UpdateItemState(rowindex, Chiavi.dict["backupon_createZIP"].ToString());

        // 后台执行归档操作,每个任务使用独立的ArchivioB1实例
        await Task.Run(() =>
        {
            using (var archivio = new ArchivioB1()) // 假设ArchivioB1实现IDisposable,否则直接实例化
            {
                ct.ThrowIfCancellationRequested();
                archivio.FileName = archivePath;
                archivio.OpenArchive(FileMode.Create);
                archivio.Comment = rowindex.ToString();
                archivio.OnOverallProgress += ArchivioB1_OnOverallProgress;
                archivio.BaseDir = source;

                ct.ThrowIfCancellationRequested();
                archivio.AddFiles("*.*");

                ct.ThrowIfCancellationRequested();
                archivio.CloseArchive();
            }
        }, ct);

        UpdateItemState(rowindex, "Complete");
        return "Complete";
    }
    catch (OperationCanceledException)
    {
        UpdateItemState(rowindex, "Cancelled");
        tbkStato.Text = Chiavi.dict["backupstate_cancelled"].ToString();
        return "Cancelled";
    }
    catch (Exception ex)
    {
        UpdateItemState(rowindex, ex.Message);
        generalerror = true;
        return "Error";
    }
}

// 封装UI更新逻辑,确保在UI线程执行
private void UpdateItemState(int rowindex, string state)
{
    if (dgwStation.Dispatcher.CheckAccess())
    {
        StationItemsList[rowindex].State = state;
        dgwStation.Items.Refresh(); // 如果用ObservableCollection绑定,可去掉此调用,自动刷新
    }
    else
    {
        dgwStation.Dispatcher.Invoke(() =>
        {
            StationItemsList[rowindex].State = state;
            dgwStation.Items.Refresh();
        });
    }
}

额外优化建议

  • 如果StationItemsList是ObservableCollection<T>,更新State后会自动触发UI刷新,可去掉dgwStation.Items.Refresh()调用,提升性能。
  • ArchivioB1_OnOverallProgress事件处理程序如果涉及UI更新,也要用Dispatcher.Invoke确保在UI线程执行。

内容的提问来源于stack exchange,提问作者Paolo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 21:22:40