[使用LinqPad示例更新了2018年4月18日-参见结束]
我的应用程序收到作业列表:
var jobs = await myDB.GetWorkItems();
(注意:我们到处都使用.ConfigureAwait(false),我只是不在这些伪代码段中显示它。)
对于每个作业,我们创建一个长期运行的Task。但是,我们不想等待这个长期运行的任务完成。
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。但显然,它无法完成,因为我仍在调试它。
其他信息
在下面的评论中,我被要求提供一些有关流程的其他信息,因为我描述的内容似乎是不可能的(事实上,我希望那是不可能的)。下面是我的代码的简化版本(我删除了取消令牌和防御性代码的检查,但没有任何影响流程的东西。)
应用程序入口点
这是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(....)。
也请注意,在调用Cancellation请求并完成所有任务之后,我将调用ClearCompletedTasksAsync()。以后再说吧。
ExecuteSingleIterationAsync
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;
}
每个处理器实现以下接口方法: 任务ExecuteAsync(作业);
正是在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();
}
}
这被称为循环的每次迭代。目的是确保未等待任务的列表保持较小。
就这样...处置似乎仅在调试时被调用。
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),但我尚未完成工作 还...
答案 0 :(得分:2)
您的问题在这一行:
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();