Geo*_*dze 142 c# parallel-extensions plinq task-parallel-library c#-4.0
这个班级CancellationTokenSource
是一次性的.快速浏览Reflector证明KernelEvent
了(很可能)非托管资源的使用.由于CancellationTokenSource
没有终结器,如果我们不处理它,GC将不会这样做.
另一方面,如果您查看MSDN文章" 托管线程中的取消"中列出的示例,则只有一个代码段处置该令牌.
在代码中处理它的正确方法是什么?
using
如果您不等待它,则无法将启动并行任务的代码包装起来.只有在你不等的时候取消才有意义.ContinueWith
通过Dispose
电话添加任务,但这是要走的路吗?.ForAll(x => Console.Write(x))
?因为它没有类似于Reset
清理IsCancelRequested
和Token
字段的方法,所以我认为它不可重复使用,因此每次启动任务(或PLINQ查询)时都应该创建一个新任务.这是真的吗?如果是,我的问题是Dispose
在这些CancellationTokenSource
案例中处理的正确和建议的策略是什么?
Gru*_*kin 67
谈到是否真的有必要调用Dispose on CancellationTokenSource
...我的项目中有内存泄漏,结果证明这CancellationTokenSource
是问题所在.
我的项目有一个服务,即不断读取数据库并触发不同的任务,我将链接的取消令牌传递给我的工作人员,所以即使他们完成数据处理后,也没有处理取消令牌,导致内存泄漏.
请注意,完成后必须调用
Dispose
链接的令牌源.有关更完整的示例,请参见如何:侦听多个取消请求.
我用ContinueWith
在我的实现中.
Jes*_*ood 36
我认为目前的答案都不令人满意.经过研究,我发现Stephen Toub的回复(参考):
这取决于.在.NET 4中,CTS.Dispose有两个主要用途.如果已经访问了CancellationToken的WaitHandle(因此懒得分配它),Dispose将处理该句柄.此外,如果CTS是通过CreateLinkedTokenSource方法创建的,则Dispose将取消CTS与其链接的标记的链接.在.NET 4.5中,Dispose有一个额外的用途,即如果CTS使用了一个定时器(例如,调用CancelAfter),则Timer将被Disposed.
使用CancellationToken.WaitHandle是非常罕见的,因此在使用Dispose之后进行清理通常不是一个很好的理由. 但是,如果您使用CreateLinkedTokenSource创建CTS,或者如果您正在使用CTS的计时器功能,则使用Dispose可能会更有影响力.
我认为大胆的部分是重要的部分.他使用"更有影响力",这使得它有点模糊.我将其解释为意味着Dispose
应该在这些情况下进行调用,否则Dispose
不需要使用.
Bry*_*sby 25
我在ILSpy中查看了CancellationTokenSource
但是我只能找到m_KernelEvent,它实际上是一个ManualResetEvent
,它是WaitHandle对象的包装类.这应该由GC正确处理.
Sam*_*eff 21
你应该随时处置CancellationTokenSource
.
如何处理它完全取决于场景.您提出了几种不同的方案.
using
只有当你正在使用CancellationTokenSource
你正在等待的某些并行工作时才有效.如果这是你的senario,那么很棒,这是最简单的方法.
使用任务时,请使用ContinueWith
您指示处置的任务CancellationTokenSource
.
对于plinq,您可以使用,using
因为您并行运行它,但等待所有并行运行的工作程序完成.
对于UI,您可以CancellationTokenSource
为每个可取消操作创建一个新的,该操作不依赖于单个取消触发器.维护List<IDisposable>
并将每个源添加到列表中,在处理组件时处置所有源.
对于线程,创建一个新线程,该线程连接所有工作线程,并在所有工作线程完成时关闭单个源.请参阅CancellationTokenSource,何时处置?
总有办法. IDisposable
应始终处置实例.样本通常不会,因为它们要么是快速样本以显示核心用法,要么因为添加所示类的所有方面对于样本而言过于复杂.样本只是一个样本,不一定(甚至通常)生产质量代码.并非所有样本都可以按原样复制到生产代码中.
jly*_*ith 15
这个答案仍在谷歌搜索中出现,我相信投票的答案并不是完整的故事.查看(CTS)和(CT)的源代码后,我相信对于大多数用例,下面的代码序列很好:CancellationTokenSource
CancellationToken
if (cancelTokenSource != null)
{
cancelTokenSource.Cancel();
cancelTokenSource.Dispose();
cancelTokenSource = null;
}
Run Code Online (Sandbox Code Playgroud)
m_kernelHandle
上面提到的内部字段是支持WaitHandle
CTS和CT类中的属性的同步对象.只有在您访问该属性时才会实例化它.因此,除非您WaitHandle
在Task
调用dispose 中使用某些旧式线程同步,否则将无效.
当然,如果您正在使用它,您应该执行上面其他答案所建议的并延迟调用,Dispose
直到WaitHandle
使用句柄的任何操作完成,因为,如WaitHandle的Windows API文档中所述,结果是未定义的.
自问了这个问题已经有很长的时间了,得到了很多有用的答案,但是我遇到了一个与此相关的有趣问题,并认为我可以将它作为另一个答案发布在这里:
CancellationTokenSource.Dispose()
仅当您确定没有人会尝试获得CTS的Token
财产时,才应致电。否则,你应该不调用它,因为它是一个比赛。例如,在这里:
https://github.com/aspnet/AspNetKatana/issues/108
在此问题的修复程序中,以前所做的代码cts.Cancel(); cts.Dispose();
被编辑为仅执行此操作,cts.Cancel();
因为不幸的是,任何人不幸地尝试获取取消令牌以便在调用Dispose 之后观察其取消状态,都将需要处理ObjectDisposedException
-除了在OperationCanceledException
他们正计划进行。
Tratcher提出了与此修复相关的另一个关键观察结果:“仅对于不会被取消的令牌才需要进行处置,因为取消会进行所有相同的清理。” 即只做Cancel()
而不是处理就足够了!
我创建了一个线程安全类,将 a 绑定CancellationTokenSource
到 a Task
,并保证CancellationTokenSource
在其关联Task
完成时将被处理。它使用锁来确保CancellationTokenSource
在处理期间或之后不会被取消。发生这种情况是为了遵守文档,其中指出:
Dispose
只有在CancellationTokenSource
对象上的所有其他操作都完成后才能使用该方法。
而且还:
该
Dispose
方法使CancellationTokenSource
处于不可用状态。
这是课程:
public class CancelableExecution
{
private readonly bool _allowConcurrency;
private Operation _activeOperation;
// Represents a cancelable operation that signals its completion when disposed
private class Operation : IDisposable
{
private readonly CancellationTokenSource _cts;
private readonly TaskCompletionSource<bool> _completionSource;
private bool _disposed;
public Task Completion => _completionSource.Task; // Never fails
public Operation(CancellationTokenSource cts)
{
_cts = cts;
_completionSource = new TaskCompletionSource<bool>(
TaskCreationOptions.RunContinuationsAsynchronously);
}
public void Cancel() { lock (this) if (!_disposed) _cts.Cancel(); }
void IDisposable.Dispose() // It is disposed once and only once
{
try { lock (this) { _cts.Dispose(); _disposed = true; } }
finally { _completionSource.SetResult(true); }
}
}
public CancelableExecution(bool allowConcurrency)
{
_allowConcurrency = allowConcurrency;
}
public CancelableExecution() : this(false) { }
public bool IsRunning => Volatile.Read(ref _activeOperation) != null;
public async Task<TResult> RunAsync<TResult>(
Func<CancellationToken, Task<TResult>> action,
CancellationToken extraToken = default)
{
if (action == null) throw new ArgumentNullException(nameof(action));
var cts = CancellationTokenSource.CreateLinkedTokenSource(extraToken);
using (var operation = new Operation(cts))
{
// Set this as the active operation
var oldOperation = Interlocked.Exchange(ref _activeOperation, operation);
try
{
if (oldOperation != null && !_allowConcurrency)
{
oldOperation.Cancel();
await oldOperation.Completion; // Continue on captured context
// The Completion never fails
}
cts.Token.ThrowIfCancellationRequested();
var task = action(cts.Token); // Invoke on the initial context
return await task.ConfigureAwait(false);
}
finally
{
// If this is still the active operation, set it back to null
Interlocked.CompareExchange(ref _activeOperation, null, operation);
}
}
// The cts is disposed along with the operation
}
public Task RunAsync(Func<CancellationToken, Task> action,
CancellationToken extraToken = default)
{
if (action == null) throw new ArgumentNullException(nameof(action));
return RunAsync<object>(async ct =>
{
await action(ct).ConfigureAwait(false);
return null;
}, extraToken);
}
public Task CancelAsync()
{
var operation = Volatile.Read(ref _activeOperation);
if (operation == null) return Task.CompletedTask;
operation.Cancel();
return operation.Completion;
}
public bool Cancel() => CancelAsync() != Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)
类的主要方法CancelableExecution
是RunAsync
和Cancel
。默认情况下不允许并发操作,这意味着RunAsync
在开始新操作之前,第二次调用将静默取消并等待前一个操作完成(如果它仍在运行)。
此类可用于任何类型的应用程序。它的主要用途是在 UI 应用程序中,在带有用于启动和取消异步操作的按钮的表单中,或者在每次更改所选项目时取消和重新启动操作的列表框。以下是第一种情况的示例:
private readonly CancelableExecution _cancelableExecution = new CancelableExecution();
private async void btnExecute_Click(object sender, EventArgs e)
{
string result;
try
{
Cursor = Cursors.WaitCursor;
btnExecute.Enabled = false;
btnCancel.Enabled = true;
result = await _cancelableExecution.RunAsync(async ct =>
{
await Task.Delay(3000, ct); // Simulate some cancelable I/O operation
return "Hello!";
});
}
catch (OperationCanceledException)
{
return;
}
finally
{
btnExecute.Enabled = true;
btnCancel.Enabled = false;
Cursor = Cursors.Default;
}
this.Text += result;
}
private void btnCancel_Click(object sender, EventArgs e)
{
_cancelableExecution.Cancel();
}
Run Code Online (Sandbox Code Playgroud)
该RunAsync
方法接受一个额外的CancellationToken
参数,该参数链接到内部创建的CancellationTokenSource
. 提供此可选令牌在高级方案中可能很有用。
归档时间: |
|
查看次数: |
40177 次 |
最近记录: |