进度报告和任务继续竞争条件

问题描述 投票:0回答:2

我有一个

LogWriter
类,它接受文件路径和
System.Windows.Forms.TextBox
,然后在
public void Write(string progress)
方法中写入两者。它使用
StreamWriter
AutoFlush
附加到文件,因此,它本身实现
IDisposable
来处理流。它只是每次设置文本框上的
Text
属性。

我在另一个类中有一个方法,它生成进程并使用

ErrorDataReceived
OutputDataReceived
事件上的事件处理程序来调用
Report()
实例上的
Progess

在表单上的异步按钮单击事件处理程序中,我执行以下操作:

using(LogWriter logger = new LogWriter(filePath, txtProgress))
{
    IProgress<string> progress = new Progress<string>(s => logger.Write(s));
    await Task.Run(() => this._C.RunProcess(progress));
} //disposal point

在此之前,我在同步处理程序中使用

ContinueWith
实现了它:

LogWriter logger = new LogWriter(filePath, txtProgress);
Task t = Task.Run(() => this._C.RunProcess(progress));
t.ContinueWith((_) => { coreCurrentLogger.Dispose(); }, TaskScheduler.FromCurrentSynchronizationContext());

我知道调用

Progress.Report()
使用
SynchonizationContext.Post()
将 lambda 排队以便稍后执行。在这种情况下,我相信
SynchonizationContext
将是 UI 线程的。

在这两种情况下,这段代码的行为都符合我的预期。

TextBox
和文件都包含由生成的进程写入输出流的最终字符串。

基于上述内容,在这两种实现中是否存在竞争条件,即在执行所有

LogWriter
lambda 之前可以处理
Process.Report()
实例?

两种实现方式有区别吗?第一个示例需要

ConfigureAwait(true)
吗?

c# .net winforms async-await
2个回答
0
投票

我知道调用

Progress.Report()
使用
SynchonizationContext.Post()
将 lambda 排队以便稍后执行。在这种情况下,我相信
SynchonizationContext
将是 UI 线程的。

正确。

在这两种实现中是否存在竞争条件,其中

LogWriter
实例可以在所有
Process.Report()
lambda 执行之前被处置?

是的,两种实现都会受到这种竞争条件的影响。

Progress<T>
类不提供任何有关异步
Report
操作完成的通知机制,因此只要使用此类,竞争条件就不可避免。您可以通过在等待
await Task.Yield();
任务后添加
Task.Run
来缓解这种竞争状况,希望底层
SynchonizationContext
按 FIFO 顺序处理计划的操作。但这并不能保证。从理论上讲,
Task.Yield
等待任务可能会以更高的优先级完成,绕过现有的预定
Report
操作。

如果您想绝对确保代码中不存在竞争条件,那么除了放弃

Progress<T>
类并使用自定义
IProgress<T>
实现之外,您别无选择。最简单的是使报告同步而不是异步。您可以在这里找到
SynchronousProgress<T>
。言下之意是,每次发出
Report
时,后台操作都会被暂停,所以会变得稍微慢一些。只要
Report
不经常发出,性能影响就不会大到引人注目。


0
投票

这里确实存在竞争条件。

Progress
Post
是处理程序,而不是发送它们,因此允许工作人员在进度报告实际发生之前继续执行,这意味着理论上整个操作可能以一个或多个进度报告处理程序结束跑步。

最简单的解决方案是将

Progress
对象包装在一个跟踪正在进行的处理程序数量的对象中,并提供
Task
在所有正在进行的处理程序完成时通知您。

公共类 TrackableProgress : IProgress { 私有 IProgress 嵌套进度; 私有 int handlersRunning = 0; 私有任务完成源?任务完成源; 公共对象键 = new object();

   public TrackableProgress(Action<T> action)
   {
       nestedProgress = new Progress<T>(Handler);

       void Handler(T t)
       {
           try
           {
               lock (key)
                   handlersRunning++;

               action(t);
           }
           finally
           {
               lock (key)
               {
                   handlersRunning--;
                   if (handlersRunning == 0)
                   {
                       taskCompletionSource?.TrySetResult();
                       taskCompletionSource = null;
                   }
               }
           }
       }
   }
   void IProgress<T>.Report(T value)
   {
       nestedProgress.Report(value);
   }
   public Task HandlersFinished()
   {
       Task? result = null;
       lock (key)
       {
           if (handlersRunning > 0)
           {
               taskCompletionSource ??= new TaskCompletionSource();
               result = taskCompletionSource.Task;
           }
       }
       return result ?? Task.CompletedTask;
   }

}

这将允许你写:

using(LogWriter logger = new LogWriter(filePath, txtProgress))
{
    TrackableProgress<string> progress = new TrackableProgress<string>(s => logger.Write(s));
    await Task.Run(() => this._C.RunProcess(progress));
    await progress.HandlersFinished();
}
© www.soinside.com 2019 - 2024. All rights reserved.