C# 在锁内使用线程池和线程完成更新

C# 在锁内使用线程池和线程完成更新,c#,threadpool,C#,Threadpool,在几篇关于线程化应用程序开发的教程之后,我实现了一个小的实践实用程序,它使用Task.Run和Threadpool.QueueUserWorkItem递归索引文件夹并生成包含的所有文件的校验和清单 虽然应用程序运行良好,但在专门使用线程池的部分中,我无法更新UI。当使用Task.Run时,使用waitApplication.Current.Dispatcher.BeginInvoke可以正确更新UI 所需的行为是当每个线程池的线程完成各自的任务时,在ProcessChecksums中继续更新进度

在几篇关于线程化应用程序开发的教程之后,我实现了一个小的实践实用程序,它使用Task.Run和
Threadpool.QueueUserWorkItem
递归索引文件夹并生成包含的所有文件的校验和清单

虽然应用程序运行良好,但在专门使用
线程池的部分中,我无法更新UI。当使用Task.Run时,使用wait
Application.Current.Dispatcher.BeginInvoke
可以正确更新UI

所需的行为是当每个线程池的线程完成各自的任务时,在
ProcessChecksums
中继续更新进度

int queuedThreads = 0;
object locker = new object();
CancellationTokenSource cancellationTokenSource;
List<string> lstFilePaths = new List<string>();
Manifest manifestData;
event Action<double> OnProgressChange;
event Action<string> OnStatusChange;
    
async void GenerateManifest(Object sender, RoutedEventArgs e) {
    status = "Indexing Files";
    progress = 1;

    cancellationTokenSource = new CancellationTokenSource();

    await Task.Run(() => Async_GenerateManifest(cancellationTokenSource.Token), cancellationTokenSource.Token);

    if (!cancellationTokenSource.Token.IsCancellationRequested) {
        Finished();
    }else{
        Cancelled();
    }
}
    
async Task Async_GenerateManifest(CancellationToken cancellationToken) {
    if (cancellationToken.IsCancellationRequested) { return; }

    Async_IndexFiles(initialpath, cancellationToken);
        
    //Works Perfectly
    await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {
        OnStatusChange("Generating Checksums");
        OnProgressChange(10);
    }));

    ProcessChecksums(cancellationToken);

    //Works Perfectly
    await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => {
        OnStatusChange("Saving Manifest");
        OnProgressChange(90);
    }));

    SaveManifestFile(cancellationToken);
}
    
void ProcessChecksums(CancellationToken cancellationToken) {
    List<FileData> lstFileData = new List<FileData>();

    for (int i = 0; i < lstFilePaths.Count; i++) {
        if (cancellationToken.IsCancellationRequested) { return; }
            
        string s = lstFilePaths[i];
        lock (locker) queuedThreads++;

        ThreadPool.QueueUserWorkItem( x => {
            manifestData.AddFileData(new FileData(s, GenerateChecksum(s)));
        });
    }

    lock (locker) {
        while (queuedThreads > 0) {
            if (cancellationToken.IsCancellationRequested) { return; }=
            Monitor.Wait(locker);
                
            //Can't use await Dispatcher.BeginInvoke as is inside a lock, doesn't update GUI while waiting.
            OnProgressChange((((queuedThreads - lstFilePaths.Count) * -1) / lstFilePaths.Count) - 0.2);
        }
    }
}

string GenerateChecksum(string filePath) {
    //Time-consuming checksum generation
    //...

    lock (locker) {
        queuedThreads--;
        Monitor.Pulse(locker);
    }

    return BitConverter.ToString(checksum);
}
int-queuedThreads=0;
对象锁定器=新对象();
CancellationTokenSource CancellationTokenSource;
List lstfilepath=新列表();
舱单数据;
事件-行动-改变;
对状态变化的事件动作;
异步void GenerateManifest(对象发送方,RoutedEventArgs e){
status=“索引文件”;
进展=1;
cancellationTokenSource=新的cancellationTokenSource();
等待任务。运行(()=>Async_GenerateManifest(cancellationTokenSource.Token),cancellationTokenSource.Token);
如果(!cancellationTokenSource.Token.IsCancellationRequested){
完成的();
}否则{
取消();
}
}
异步任务async_GenerateManifest(CancellationToken CancellationToken){
if(cancellationToken.IsCancellationRequested){return;}
异步索引文件(initialpath、cancellationToken);
//完美地工作
等待应用程序.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,新操作(()=>{
OnStatusChange(“生成校验和”);
变化(10);
}));
处理校验和(取消令牌);
//完美地工作
等待应用程序.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,新操作(()=>{
OnStatusChange(“保存舱单”);
变化(90);
}));
SaveManifestFile(cancellationToken);
}
无效处理校验和(CancellationToken CancellationToken){
List lstFileData=新列表();
对于(int i=0;i{
AddFileData(新文件数据,GenerateChecksum);
});
}
锁(储物柜){
while(queuedThreads>0){
if(cancellationToken.IsCancellationRequested){return;}=
监视器。等待(储物柜);
//无法使用Wait Dispatcher.BeginInvoke,因为它位于锁中,在等待时不更新GUI。
OnProgressChange(((queuedThreads-lstfilePath.Count)*-1)/lstfilePath.Count)-0.2);
}
}
}
字符串GenerateChecksum(字符串文件路径){
//耗时的校验和生成
//...
锁(储物柜){
队列线程--;
监视器。脉冲(锁定器);
}
返回BitConverter.ToString(校验和);
}

使用后台线程的进度更新更新UI的标准模式是使用
IProgress
/
progress
。与直接使用
Dispatcher
相比,更现代的方法有几个好处

// You'd want to set these while on the UI thread. // E.g., in your constructor: // _percentProgress = new Progress<double>(value => ...); // _statusProgress = new Progress<string>(value => ...); IProgress<double> _percentProgress; IProgress<string> _statusProgress; async void GenerateManifest(Object sender, RoutedEventArgs e) { status = "Indexing Files"; progress = 1; cancellationTokenSource = new CancellationTokenSource(); await Task.Run(() => GenerateManifest(cancellationTokenSource.Token)); if (!cancellationTokenSource.Token.IsCancellationRequested) { Finished(); }else{ Cancelled(); } } void GenerateManifest(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return; } Async_IndexFiles(initialpath, cancellationToken); _statusProgress?.Report("Generating Checksums"); _percentProgress?.Report(10); ProcessChecksums(cancellationToken); _statusProgress?.Report("Saving Manifest"); _percentProgress?.Report(90); SaveManifestFile(cancellationToken); } void ProcessChecksums(CancellationToken cancellationToken) { List<FileData> lstFileData = new List<FileData>(); for (int i = 0; i < lstFilePaths.Count; i++) { if (cancellationToken.IsCancellationRequested) { return; } string s = lstFilePaths[i]; lock (locker) queuedThreads++; ThreadPool.QueueUserWorkItem( x => { manifestData.AddFileData(new FileData(s, GenerateChecksum(s))); }); } lock (locker) { while (queuedThreads > 0) { if (cancellationToken.IsCancellationRequested) { return; } Monitor.Wait(locker); _percentProgress?.Report((((queuedThreads - lstFilePaths.Count) * -1) / lstFilePaths.Count) - 0.2); } } } //您可能希望在UI线程上设置这些。 //例如,在您的构造函数中: //_percentProgress=新进度(值=>…); //_statusProgress=新进度(值=>…); i进展(百分比进展);; i进展(i)状态进展;; 异步void GenerateManifest(对象发送方,RoutedEventArgs e){ status=“索引文件”; 进展=1; cancellationTokenSource=新的cancellationTokenSource(); wait Task.Run(()=>GenerateManifest(cancellationTokenSource.Token)); 如果(!cancellationTokenSource.Token.IsCancellationRequested){ 完成的(); }否则{ 取消(); } } void GenerateManifest(CancellationToken CancellationToken){ if(cancellationToken.IsCancellationRequested){return;} 异步索引文件(initialpath、cancellationToken); _statusProgress?报告(“生成校验和”); _进度百分比?报告(10); 处理校验和(取消令牌); _statusProgress?报告(“保存清单”); _进度百分比?报告(90); SaveManifestFile(cancellationToken); } 无效处理校验和(CancellationToken CancellationToken){ List lstFileData=新列表(); 对于(int i=0;i{ AddFileData(新文件数据,GenerateChecksum); }); } 锁(储物柜){ while(queuedThreads>0){ if(cancellationToken.IsCancellationRequested){return;} 监视器。等待(储物柜); _percentProgress?.Report(((queuedThreads-lstfilepath.Count)*-1)/lstfilepath.Count)-0.2); } } }
代码还有一些其他问题
QueueUserWorkItem
可能应替换为
Task。运行
等待任务。当所有
发生异常时,确保不会中断进程。
如果(…)完成();否则取消()Task
,可能最能代表code>。使用
throwifccancellationrequested
而不是
IsCancellationRequested
。还有一种
监视器。没有
脉冲的等待
,只是用于进度更新,这很奇怪;允许代码报告自己的进度会更干净。如果您单独查看其中的每一个,您应该会发现您的代码更干净。

使用后台线程的进度更新更新UI的标准模式是使用
IProgress
/