c# - 识别长时间运行的异步任务何时完成
问题描述
[2018 年 4 月 18 日更新,使用 LinqPad 示例 - 见结尾]
我的应用程序收到一份工作列表:
var jobs = await myDB.GetWorkItems();
(注意:我们在任何地方都使用 .ConfigureAwait(false),我只是没有在这些伪代码片段中显示它。)
对于每个作业,我们创建一个长时间运行的任务。但是,我们不想等待这个长时间运行的任务完成。
jobs.ForEach(job =>
{
var module = Factory.GetModule(job.Type);
var task = Task.Run(() => module.ExecuteAsync(job.Data));
this.NonAwaitedTasks.Add(task, module);
};
任务及其相关模块实例都被添加到 ConcurrentDictionary 中,这样它们就不会超出范围。
在其他地方,我有另一种偶尔调用的方法,其中包含以下内容:
foreach (var entry in this.NonAwaitedTasks.Where(e => e.Key.IsCompleted))
{
var module = entry.Value as IDisposable;
module?.Dispose();
this.NonAwaitedTasks.Remove(entry.Key);
}
(注意,NonAwaitedTasks 还使用 SemaphoreSlim 锁定...)
所以,这个想法是这个方法会找到所有那些已经完成的任务,然后处理它们的相关模块,并将它们从这个字典中删除。
然而....
在 Visual Studio 2017 中进行调试时,我从数据库中提取了一个作业,并且在我花时间在已实例化的单个模块中进行调试时,在该模块上调用了 Dispose。查看Callstack,我可以看到在上面的方法中已经调用了Dispose,那是因为任务有IsCompleted == true。但显然,它无法完成,因为我还在调试它。
- .IsCompleted 属性是要检查的错误属性吗?
- 这只是在 Visual Studio 中调试的产物吗?
- 我会以错误的方式解决这个问题吗?
附加信息
在下面的评论中,我被要求提供一些关于流程的额外信息,因为我所描述的似乎不可能(事实上,我希望它不可能)。下面是我的代码的精简版本(我已经删除了对取消令牌和防御性编码的检查,但没有任何影响流程)。
应用程序入口点
这是一个 Windows 服务。在 OnStart() 中是以下行:
this.RunApplicationTask =
Task.Run(() => myApp.DoWorkAsync().ConfigureAwait(false), myService.CancelSource.Token);
“RunApplicationTask”只是一个在服务生命周期内将执行任务保持在范围内的属性。
DoWorkAsync()
public async Task DoWorkAsync()
{
do
{
await this.ExecuteSingleIterationAsync().ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
}
while (myApp.ServiceCancellationToken.IsCancellationRequested == false);
await Task.WhenAll(this.NonAwaitedTasks.Keys).ConfigureAwait(false);
await this.ClearCompletedTasksAsync().ConfigureAwait(false);
this.WorkItemsTaskCompletionSource.SetResult(true);
return;
}
因此,当我在调试时,这是在迭代 DO-LOOP,它没有到达 Task.WhenAll(....)。
还要注意,在调用取消请求并且所有任务都完成后,我调用 ClearCompletedTasksAsync()。稍后再详述....
执行单迭代异步
private async Task ExecuteSingleIterationAsync()
{
var getJobsResponse = await DB.GetJobsAsync().ConfigureAwait(false);
await this.ProcessWorkLoadAsync(getJobsResponse.Jobs).ConfigureAwait(false);
await this.ClearCompletedTasksAsync().ConfigureAwait(false);
}
ProcessWorkLoadAsync
private async Task ProcessWorkLoadAsync(IList<Job> jobs)
{
if (jobs.NoItems())
{
return ;
}
jobs.ForEach(job =>
{
// The processor instance is disposed of when removed from the NonAwaitedTasks collection.
IJobProcessor processor = ProcessorFactory.GetProcessor(workItem, myApp.ServiceCancellationToken);
try
{
var task = Task.Run(() => processor.ExecuteAsync(job).ConfigureAwait(false), myApp.ServiceCancellationToken);
this.NonAwaitedTasks.Add(task, processor);
}
catch (Exception e)
{
...
}
});
return;
}
每个处理器实现以下接口方法: Task ExecuteAsync(Job job);
当我在 ExecuteAsync 中时,.Dispose() 在我正在使用的处理器实例上被调用。
ProcessorFactory.GetProcessor()
public static IJobProcessor GetProcessor(Job job, CancellationToken token)
{
.....
switch (someParamCalculatedAbove)
{
case X:
{
return new XProcessor(...);
}
case Y:
{
return new YProcessor(...);
}
default:
{
return null;
}
}
}
所以在这里我们得到了一个新的实例。
ClearCompletedTasksAsync()
private async Task ClearCompletedTasksAsync()
{
await myStatic.NonAwaitedTasksPadlock.WaitAsync().ConfigureAwait(false);
try
{
foreach (var taskEntry in this.NonAwaitedTasks.Where(entry => entry.Key.IsCompleted).ToArray())
{
var processorInstance = taskEntry.Value as IDisposable;
processorInstance?.Dispose();
this.NonAwaitedTasks.Remove(taskEntry.Key);
}
}
finally
{
myStatic.NonAwaitedTasksPadlock.Release();
}
}
这称为 Do-Loop 的每次迭代。其目的是确保非等待任务列表保持较小。
就是这样...... Dispose 似乎只在调试时被调用。
LinqPad 示例
async Task Main()
{
SetProcessorRunning();
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
do
{
foreach (var entry in NonAwaitedTasks.Where(e => e.Key.IsCompleted).ToArray())
{
"Task is completed, so will dispose of the Task's processor...".Dump();
var p = entry.Value as IDisposable;
p?.Dispose();
NonAwaitedTasks.Remove(entry.Key);
}
}
while (NonAwaitedTasks.Count > 0);
}
// Define other methods and classes here
public void SetProcessorRunning()
{
var p = new Processor();
var task = Task.Run(() => p.DoWorkAsync().ConfigureAwait(false));
NonAwaitedTasks.Add(task, p);
}
public interface IProcessor
{
Task DoWorkAsync();
}
public static Dictionary<Task, IProcessor> NonAwaitedTasks = new Dictionary<Task, IProcessor>();
public class Processor : IProcessor, IDisposable
{
bool isDisposed = false;
public void Dispose()
{
this.isDisposed = true;
"I have been disposed of".Dump();
}
public async Task DoWorkAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
if (this.isDisposed)
{
$"I have been disposed of (isDispose = {this.isDisposed}) but I've not finished work yet...".Dump();
}
await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
}
}
输出:
任务已完成,因此将处置任务的处理器...
我被处理掉了
我已经被处理掉了(isDispose = True),但我还没有完成工作......
解决方案
您的问题出在这一行:
var task = Task.Run(() => p.DoWorkAsync().ConfigureAwait(false));
将鼠标悬停在 上var
并查看它是什么类型。
Task.Run
async
通过为Func<Task<Task>>
和朋友制定特殊的“任务展开”规则来理解代表。但它不会对Func<ConfiguredTaskAwaitable>
.
你可以这样想;使用上面的代码:
p.DoWorkAsync()
返回一个Task
。Task.ConfigureAwait(false)
返回一个ConfiguredTaskAwaitable
。- 所以
Task.Run
被要求运行这个ConfiguredTaskAwaitable
在线程池线程上创建一个的函数。 - 因此,返回类型
Task.Run
是Task<ConfiguredTaskAwaitable>
- 一旦ConfiguredTaskAwaitable
创建就完成的任务。当它被创建时——而不是当它完成时。
在这种情况下,ConfigureAwait(false)
无论如何都不会做任何事情,因为没有await
配置。所以你可以删除它:
var task = Task.Run(() => p.DoWorkAsync());
此外,正如 Servy 所提到的,如果您不需要在线程池线程上运行DoWorkAsync
,您也可以跳过Task.Run
:
var task = p.DoWorkAsync();
推荐阅读
- java - Does MyBatis follows JPA?
- apache - 浏览器的Linux目录快捷方式路径
- javascript - 如何引用在同一类中创建的事件侦听器中的类?
- java - 将 Android Studio 项目连接到 Firebase 数据库的问题
- wordpress - 联系表格 7 wordpress 插件消息条件
- javascript - 可缩放的旭日形
- javascript - 当我有n个ID时,如何在鼠标悬停时显示特定的div?
- android - 拍照时捆绑键返回 null
- android - How to Stop ads on splash activity
- reactjs - 避免使用单元素组件的双组件