表单方法使用取消令牌停止任务并等待任务完成。停止方法永不屈服,任务也不会停止
Form method stops Task with cancellation token and waits for task to complete. Stop Method never yields and Task does not stop
我有一项服务 Class 可以启动和停止一项任务,该任务旨在 运行 在表单应用程序的后台进行。
停止监控时:
一旦调用了 Stop()
方法,Monitor Task 上就没有断点,并且 IsStarted
永远不会变为 false' 并陷入无限循环。
这有什么问题吗?为什么监控任务似乎停止了 运行ning?我该如何解决这个问题?
private volatile bool IsStarted = false;
public void Start(PartList partList, UnioServiceSettings settings, Action<string, string, string> createSummaryFile)
{
_Settings = settings;
_PartList = partList;
_cts = new CancellationTokenSource();
Task.Run(async() => await Monitor(_cts.Token)); //no difference:.ConfigureAwait(false);
_ProgressMessage?.Report("Monitor Started...");
IsStarted = true;
}
public async Task<bool> Stop()
{
if (IsStarted)
{
_ProgressMessage?.Report("Stopping Monitor...");
_cts.Cancel();
while (IsStarted) //this loop runs forever
{
await Task.Delay(1000); //.ConfigureAwait(false);? no difference
}
return true;
}
else
{
_ProgressMessage?.Report("Cannot stop Monitor. It has not been started.");
return false;
}
}
private async Task Monitor(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//do stuff
await Task.Delay(_Settings.DelayMs,token).ConfigureAwait(false);
}
IsStarted = false;
return;
}
这里存在竞争条件:
Task.Run(async() => await Monitor(_cts.Token));
_ProgressMessage?.Report("Monitor Started...");
IsStarted = true;
Task.Run
方法创建的 Task
有可能在当前线程调用 IsStarted = true;
之前在另一个线程上完成。所以最终 IsStarted
将稳定在值 true
而没有 Task
运行。要解决此问题,您可以将更改 IsStarted
字段的责任专门转移到 Monitor
方法本身:
private async Task Monitor(CancellationToken token)
{
IsStarted = true;
try
{
_ProgressMessage?.Report("Monitor Started...");
while (true)
{
token.ThrowIfCancellationRequested();
//do stuff
await Task.Delay(_Settings.DelayMs,token).ConfigureAwait(false);
}
}
finally
{
IsStarted = false;
}
}
不过,我不知道这种竞争条件是否是导致您出现问题的原因。
更新: 这是实现服务的另一种方法,其中 bool IsStarted
字段已替换为 Task _monitorTask
字段。此实现假设调用者不会同时调用方法 Start
和 Stop
。在 SemaphoreSlim(1, 1)
.
的帮助下,它实际上执行了 no-concurrency 策略
private readonly SemaphoreSlim _semaphore = new(1, 1);
private CancellationTokenSource _cts;
private Task _monitorTask;
private IProgress<string> _progressMessage;
public void Start()
{
if (!_semaphore.Wait(0)) throw new InvalidOperationException("Concurrency!");
try
{
if (_monitorTask != null && !_monitorTask.IsCompleted)
throw new InvalidOperationException("The Monitor is already running!");
_progressMessage?.Report("Starting Monitor...");
_cts = new();
CancellationToken token = _cts.Token;
_monitorTask = Task.Run(() => MonitorAsync(token), token);
}
finally { _semaphore.Release(); }
}
public async Task<bool> Stop()
{
if (!_semaphore.Wait(0)) throw new InvalidOperationException("Concurrency!");
try
{
if (_monitorTask == null)
{
_progressMessage?.Report("Monitor has not been started.");
return false;
}
else if (_monitorTask.IsCompleted)
{
_progressMessage?.Report("Monitor is already stopped.");
return false;
}
else
{
_progressMessage?.Report("Stopping Monitor...");
_cts?.Cancel();
try { await _monitorTask; } catch { } // Ignore exceptions here
_cts?.Dispose(); _cts = null;
return true;
}
}
finally { _semaphore.Release(); }
}
private async Task MonitorAsync(CancellationToken token)
{
try
{
_progressMessage?.Report("Monitor started.");
while (true)
{
// Do stuff
await Task.Delay(1000, token);
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
_progressMessage?.Report("Monitor stopped.");
}
catch (Exception ex)
{
_progressMessage?.Report($"Monitor failed: {ex.Message}");
}
}
请注意,_cts.Token
存储在 Start
方法内的局部变量中,以防止可能的竞争条件。 Task.Run
操作在另一个线程上调用,很可能是在 Start
方法完成之后。我们不希望此操作与 _cts
字段交互,该字段必须仅从 concurrency-protected 区域处理。
我有一项服务 Class 可以启动和停止一项任务,该任务旨在 运行 在表单应用程序的后台进行。
停止监控时:
一旦调用了 Stop()
方法,Monitor Task 上就没有断点,并且 IsStarted
永远不会变为 false' 并陷入无限循环。
这有什么问题吗?为什么监控任务似乎停止了 运行ning?我该如何解决这个问题?
private volatile bool IsStarted = false;
public void Start(PartList partList, UnioServiceSettings settings, Action<string, string, string> createSummaryFile)
{
_Settings = settings;
_PartList = partList;
_cts = new CancellationTokenSource();
Task.Run(async() => await Monitor(_cts.Token)); //no difference:.ConfigureAwait(false);
_ProgressMessage?.Report("Monitor Started...");
IsStarted = true;
}
public async Task<bool> Stop()
{
if (IsStarted)
{
_ProgressMessage?.Report("Stopping Monitor...");
_cts.Cancel();
while (IsStarted) //this loop runs forever
{
await Task.Delay(1000); //.ConfigureAwait(false);? no difference
}
return true;
}
else
{
_ProgressMessage?.Report("Cannot stop Monitor. It has not been started.");
return false;
}
}
private async Task Monitor(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
//do stuff
await Task.Delay(_Settings.DelayMs,token).ConfigureAwait(false);
}
IsStarted = false;
return;
}
这里存在竞争条件:
Task.Run(async() => await Monitor(_cts.Token));
_ProgressMessage?.Report("Monitor Started...");
IsStarted = true;
Task.Run
方法创建的 Task
有可能在当前线程调用 IsStarted = true;
之前在另一个线程上完成。所以最终 IsStarted
将稳定在值 true
而没有 Task
运行。要解决此问题,您可以将更改 IsStarted
字段的责任专门转移到 Monitor
方法本身:
private async Task Monitor(CancellationToken token)
{
IsStarted = true;
try
{
_ProgressMessage?.Report("Monitor Started...");
while (true)
{
token.ThrowIfCancellationRequested();
//do stuff
await Task.Delay(_Settings.DelayMs,token).ConfigureAwait(false);
}
}
finally
{
IsStarted = false;
}
}
不过,我不知道这种竞争条件是否是导致您出现问题的原因。
更新: 这是实现服务的另一种方法,其中 bool IsStarted
字段已替换为 Task _monitorTask
字段。此实现假设调用者不会同时调用方法 Start
和 Stop
。在 SemaphoreSlim(1, 1)
.
private readonly SemaphoreSlim _semaphore = new(1, 1);
private CancellationTokenSource _cts;
private Task _monitorTask;
private IProgress<string> _progressMessage;
public void Start()
{
if (!_semaphore.Wait(0)) throw new InvalidOperationException("Concurrency!");
try
{
if (_monitorTask != null && !_monitorTask.IsCompleted)
throw new InvalidOperationException("The Monitor is already running!");
_progressMessage?.Report("Starting Monitor...");
_cts = new();
CancellationToken token = _cts.Token;
_monitorTask = Task.Run(() => MonitorAsync(token), token);
}
finally { _semaphore.Release(); }
}
public async Task<bool> Stop()
{
if (!_semaphore.Wait(0)) throw new InvalidOperationException("Concurrency!");
try
{
if (_monitorTask == null)
{
_progressMessage?.Report("Monitor has not been started.");
return false;
}
else if (_monitorTask.IsCompleted)
{
_progressMessage?.Report("Monitor is already stopped.");
return false;
}
else
{
_progressMessage?.Report("Stopping Monitor...");
_cts?.Cancel();
try { await _monitorTask; } catch { } // Ignore exceptions here
_cts?.Dispose(); _cts = null;
return true;
}
}
finally { _semaphore.Release(); }
}
private async Task MonitorAsync(CancellationToken token)
{
try
{
_progressMessage?.Report("Monitor started.");
while (true)
{
// Do stuff
await Task.Delay(1000, token);
}
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
_progressMessage?.Report("Monitor stopped.");
}
catch (Exception ex)
{
_progressMessage?.Report($"Monitor failed: {ex.Message}");
}
}
请注意,_cts.Token
存储在 Start
方法内的局部变量中,以防止可能的竞争条件。 Task.Run
操作在另一个线程上调用,很可能是在 Start
方法完成之后。我们不希望此操作与 _cts
字段交互,该字段必须仅从 concurrency-protected 区域处理。