检测父任务取消的正确方法是什么?
What is the proper way to detect parent Task cancellation?
我正在开发一个概念验证应用程序,该应用程序使用任务和信号量对数字列表进行分解,目前我有一个任务列表,List<Task>
,需要 FactorNumberClass
然后计算FactorNumberClass
中特定数字的因子目前可以正常工作。对于每个任务 T,我有一个 ContinueWith
任务更新因式分解总数的进度、因式分解的平均时间,并更新进度条,其值为(已成功分解的数字)/(待分解的总数)分解)。当分解这些 Tasks
时,输入一个 SemaphoreSlim.Wait(cancelToken)
,将当前分解限制为 5 个活动 Tasks
。最后,我有一个 ContinueWhenAll
记录所有任务完成的时间。假设没有取消,这一切都如我所愿。
当我尝试取消任务时出现问题,我无法检测任务是否已被取消,因此无法准确判断号码是否已成功分解或是否被取消。如何检测父任务是否已取消或 运行 完成?
取消令牌定义:
public static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static CancellationToken ct = tokenSource.Token;
因子Class代码:
public class FactorNumberClass
{
public FactorNumberClass()
{
}
public FactorNumberClass(int num, int threadnum)
{
this.number = num;
this.threadNumber = threadnum;
}
public List<int> factors = new List<int>();
public int number;
public int max;
public int threadNumber;
}
因式分解法:
public void Factor(FactorNumberClass F, CancellationToken token)
{
LogtoStatusText("Thread: " + F.threadNumber + " Trying to enter semaphore");
try
{
ASemaphore.Wait(ct);
F.max = (int)Math.Sqrt(F.number); //round down
for (int factor = 1; factor <= F.max; ++factor)
{ //test from 1 to the square root, or the int below it, inclusive.
if (F.number % factor == 0)
{
F.factors.Add(factor);
if (factor != F.number / factor)
{
F.factors.Add(F.number / factor);
}
}
}
F.factors.Sort();
Thread.Sleep(F.number * 300);
LogtoStatusText("Task: " + F.threadNumber + " Completed - Factors: " + string.Join(",", F.factors.ToArray()));
LogtoStatusText("Thread: " + F.threadNumber + " Releases semaphore with previous count: " + ASemaphore.Release());
}
catch (OperationCanceledException ex)
{
LogtoStatusText("Thread: " + F.threadNumber + " Cancelled.");
}
finally
{
}
}
启动处理的方法:
public void btnStart_Click(object sender, RoutedEventArgs e)
{
Task T;
List<Task> TaskList = new List<Task>();
LogtoStatusText("**** Begin creating tasks *****");
s1.Start();
AProject.FactorClassList.ForEach((f) =>
{
T = new Task(((x) => { OnUIThread(() => { RunningTasks++; }); Factor(f, ct); }), ct);
T.ContinueWith((y) =>
{
if (y.IsCompleted)
{
AProject.TotalProcessedAccounts++;
AProject.AverageProcessTime = (((Double)AProject.TotalProcessedAccounts / s1.ElapsedMilliseconds) * 1000);
}
OnUIThread(() => { RunningTasks--; });
OnUIThread(() => { UpdateCounts(AProject); });
});
TaskList.Add(T);
});
try
{
Task.Factory.ContinueWhenAll(TaskList.ToArray(), (z) => { LogtoStatusText("**** Completed all Tasks *****"); OnUIThread(() => { UpdateCounts(AProject); }); });
}
catch (AggregateException a)
{
// For demonstration purposes, show the OCE message.
foreach (var v in a.InnerExceptions)
LogtoStatusText("msg: " + v.Message);
}
LogtoStatusText("**** All tasks have been initialized, begin processing *****");
TaskList.ForEach(t => t.Start());
}
释放finally
块中的信号量,使其始终正确释放。无需检测取消。
此外,隐藏在日志消息中的副作用也不是好的风格:
LogtoStatusText("..." + ASemaphore.Release());
我是通过文字搜索才找到的。否则永远不会注意到这个错误。
使用取消令牌:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var tokenSource2 = new CancellationTokenSource();
CancellationToken ct = tokenSource2.Token;
var task = Task.Factory.StartNew(() =>
{
// Were we already canceled?
ct.ThrowIfCancellationRequested();
bool moreToDo = true;
while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
}
}, tokenSource2.Token); // Pass same token to StartNew.
tokenSource2.Cancel();
// Just continue on this thread, or Wait/WaitAll with try-catch:
try
{
task.Wait();
}
catch (AggregateException e)
{
foreach (var v in e.InnerExceptions)
Console.WriteLine(e.Message + " " + v.Message);
}
finally
{
tokenSource2.Dispose();
}
Console.ReadKey();
}
}
https://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx
我终于找到了我正在寻找的解决方案,它允许我启动(Start()
)我所有的 Task
对象,运行 它们通过信号量,观察 CancellationToken
,然后检测Task
是否被取消或正常完成。在这种情况下,如果 Task
在 CancellationTokenSource.Cancel()
被触发之前进入信号量并开始处理,它只会 "complete normally"。
这个答案:Elegantly handle task cancellation 把我推向了正确的方向。我最终捕获了 OperationCancelledException
,记录它,然后重新抛出它,以便在 ContinueWith
Task
中进行检查
这是解决我的问题的更新代码
因素Class:
private void Factor(FactorNumberClass F)
{
LogtoStatusText("Thread: " + F.threadNumber + " Trying to enter semaphore");
try
{
ASemaphore.Wait(ct);
F.max = (int)Math.Sqrt(F.number); //round down
for (int factor = 1; factor <= F.max; ++factor)
{ //test from 1 to the square root, or the int below it, inclusive.
if (F.number % factor == 0)
{
F.factors.Add(factor);
if (factor != F.number / factor)
{
F.factors.Add(F.number / factor);
}
}
}
F.factors.Sort();
Thread.Sleep(F.number * 300);
LogtoStatusText("Task: " + F.threadNumber + " Completed - Factors: " + string.Join(",", F.factors.ToArray()));
LogtoStatusText("Thread: " + F.threadNumber + " Releases semaphore with previous count: " + ASemaphore.Release());
}
catch
{
LogtoStatusText("Thread: " + F.threadNumber + " Cancelled");
throw;
}
finally
{
}
}
处理方式:
public void btnStart_Click(object sender, RoutedEventArgs e)
{
LaunchTasks();
}
private void LaunchTasks()
{
Task T;
List<Task> TaskList = new List<Task>();
LogtoStatusText("**** Begin creating tasks *****");
s1.Start();
AProject.FactorClassList.ForEach((f) =>
{
T = new Task(((x) => { OnUIThread(() => { RunningTasks++; }); Factor(f); }), ct);
T.ContinueWith((y) =>
{
if (y.Exception != null)
{
// LogtoStatusText(y.Status + " with "+y.Exception.InnerExceptions[0].GetType()+": "+ y.Exception.InnerExceptions[0].Message);
}
if (!y.IsFaulted)
{
AProject.TotalProcessedAccounts++;
AProject.AverageProcessTime = (((Double)AProject.TotalProcessedAccounts / s1.ElapsedMilliseconds) * 1000);
}
OnUIThread(() => { RunningTasks--; });
OnUIThread(() => { UpdateCounts(AProject); });
});
TaskList.Add(T);
});
try
{
Task.Factory.ContinueWhenAll(TaskList.ToArray(), (z) => { LogtoStatusText("**** Completed all Tasks *****"); OnUIThread(() => { UpdateCounts(AProject); }); });
}
catch (AggregateException a)
{
// For demonstration purposes, show the OCE message.
foreach (var v in a.InnerExceptions)
LogtoStatusText("msg: " + v.Message);
}
LogtoStatusText("**** All tasks have been initialized, begin processing *****");
TaskList.ForEach(t => t.Start());
}
我正在开发一个概念验证应用程序,该应用程序使用任务和信号量对数字列表进行分解,目前我有一个任务列表,List<Task>
,需要 FactorNumberClass
然后计算FactorNumberClass
中特定数字的因子目前可以正常工作。对于每个任务 T,我有一个 ContinueWith
任务更新因式分解总数的进度、因式分解的平均时间,并更新进度条,其值为(已成功分解的数字)/(待分解的总数)分解)。当分解这些 Tasks
时,输入一个 SemaphoreSlim.Wait(cancelToken)
,将当前分解限制为 5 个活动 Tasks
。最后,我有一个 ContinueWhenAll
记录所有任务完成的时间。假设没有取消,这一切都如我所愿。
当我尝试取消任务时出现问题,我无法检测任务是否已被取消,因此无法准确判断号码是否已成功分解或是否被取消。如何检测父任务是否已取消或 运行 完成?
取消令牌定义:
public static CancellationTokenSource tokenSource = new CancellationTokenSource();
public static CancellationToken ct = tokenSource.Token;
因子Class代码:
public class FactorNumberClass
{
public FactorNumberClass()
{
}
public FactorNumberClass(int num, int threadnum)
{
this.number = num;
this.threadNumber = threadnum;
}
public List<int> factors = new List<int>();
public int number;
public int max;
public int threadNumber;
}
因式分解法:
public void Factor(FactorNumberClass F, CancellationToken token)
{
LogtoStatusText("Thread: " + F.threadNumber + " Trying to enter semaphore");
try
{
ASemaphore.Wait(ct);
F.max = (int)Math.Sqrt(F.number); //round down
for (int factor = 1; factor <= F.max; ++factor)
{ //test from 1 to the square root, or the int below it, inclusive.
if (F.number % factor == 0)
{
F.factors.Add(factor);
if (factor != F.number / factor)
{
F.factors.Add(F.number / factor);
}
}
}
F.factors.Sort();
Thread.Sleep(F.number * 300);
LogtoStatusText("Task: " + F.threadNumber + " Completed - Factors: " + string.Join(",", F.factors.ToArray()));
LogtoStatusText("Thread: " + F.threadNumber + " Releases semaphore with previous count: " + ASemaphore.Release());
}
catch (OperationCanceledException ex)
{
LogtoStatusText("Thread: " + F.threadNumber + " Cancelled.");
}
finally
{
}
}
启动处理的方法:
public void btnStart_Click(object sender, RoutedEventArgs e)
{
Task T;
List<Task> TaskList = new List<Task>();
LogtoStatusText("**** Begin creating tasks *****");
s1.Start();
AProject.FactorClassList.ForEach((f) =>
{
T = new Task(((x) => { OnUIThread(() => { RunningTasks++; }); Factor(f, ct); }), ct);
T.ContinueWith((y) =>
{
if (y.IsCompleted)
{
AProject.TotalProcessedAccounts++;
AProject.AverageProcessTime = (((Double)AProject.TotalProcessedAccounts / s1.ElapsedMilliseconds) * 1000);
}
OnUIThread(() => { RunningTasks--; });
OnUIThread(() => { UpdateCounts(AProject); });
});
TaskList.Add(T);
});
try
{
Task.Factory.ContinueWhenAll(TaskList.ToArray(), (z) => { LogtoStatusText("**** Completed all Tasks *****"); OnUIThread(() => { UpdateCounts(AProject); }); });
}
catch (AggregateException a)
{
// For demonstration purposes, show the OCE message.
foreach (var v in a.InnerExceptions)
LogtoStatusText("msg: " + v.Message);
}
LogtoStatusText("**** All tasks have been initialized, begin processing *****");
TaskList.ForEach(t => t.Start());
}
释放finally
块中的信号量,使其始终正确释放。无需检测取消。
此外,隐藏在日志消息中的副作用也不是好的风格:
LogtoStatusText("..." + ASemaphore.Release());
我是通过文字搜索才找到的。否则永远不会注意到这个错误。
使用取消令牌:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var tokenSource2 = new CancellationTokenSource();
CancellationToken ct = tokenSource2.Token;
var task = Task.Factory.StartNew(() =>
{
// Were we already canceled?
ct.ThrowIfCancellationRequested();
bool moreToDo = true;
while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
}
}, tokenSource2.Token); // Pass same token to StartNew.
tokenSource2.Cancel();
// Just continue on this thread, or Wait/WaitAll with try-catch:
try
{
task.Wait();
}
catch (AggregateException e)
{
foreach (var v in e.InnerExceptions)
Console.WriteLine(e.Message + " " + v.Message);
}
finally
{
tokenSource2.Dispose();
}
Console.ReadKey();
}
}
https://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx
我终于找到了我正在寻找的解决方案,它允许我启动(Start()
)我所有的 Task
对象,运行 它们通过信号量,观察 CancellationToken
,然后检测Task
是否被取消或正常完成。在这种情况下,如果 Task
在 CancellationTokenSource.Cancel()
被触发之前进入信号量并开始处理,它只会 "complete normally"。
这个答案:Elegantly handle task cancellation 把我推向了正确的方向。我最终捕获了 OperationCancelledException
,记录它,然后重新抛出它,以便在 ContinueWith
Task
这是解决我的问题的更新代码
因素Class:
private void Factor(FactorNumberClass F)
{
LogtoStatusText("Thread: " + F.threadNumber + " Trying to enter semaphore");
try
{
ASemaphore.Wait(ct);
F.max = (int)Math.Sqrt(F.number); //round down
for (int factor = 1; factor <= F.max; ++factor)
{ //test from 1 to the square root, or the int below it, inclusive.
if (F.number % factor == 0)
{
F.factors.Add(factor);
if (factor != F.number / factor)
{
F.factors.Add(F.number / factor);
}
}
}
F.factors.Sort();
Thread.Sleep(F.number * 300);
LogtoStatusText("Task: " + F.threadNumber + " Completed - Factors: " + string.Join(",", F.factors.ToArray()));
LogtoStatusText("Thread: " + F.threadNumber + " Releases semaphore with previous count: " + ASemaphore.Release());
}
catch
{
LogtoStatusText("Thread: " + F.threadNumber + " Cancelled");
throw;
}
finally
{
}
}
处理方式:
public void btnStart_Click(object sender, RoutedEventArgs e)
{
LaunchTasks();
}
private void LaunchTasks()
{
Task T;
List<Task> TaskList = new List<Task>();
LogtoStatusText("**** Begin creating tasks *****");
s1.Start();
AProject.FactorClassList.ForEach((f) =>
{
T = new Task(((x) => { OnUIThread(() => { RunningTasks++; }); Factor(f); }), ct);
T.ContinueWith((y) =>
{
if (y.Exception != null)
{
// LogtoStatusText(y.Status + " with "+y.Exception.InnerExceptions[0].GetType()+": "+ y.Exception.InnerExceptions[0].Message);
}
if (!y.IsFaulted)
{
AProject.TotalProcessedAccounts++;
AProject.AverageProcessTime = (((Double)AProject.TotalProcessedAccounts / s1.ElapsedMilliseconds) * 1000);
}
OnUIThread(() => { RunningTasks--; });
OnUIThread(() => { UpdateCounts(AProject); });
});
TaskList.Add(T);
});
try
{
Task.Factory.ContinueWhenAll(TaskList.ToArray(), (z) => { LogtoStatusText("**** Completed all Tasks *****"); OnUIThread(() => { UpdateCounts(AProject); }); });
}
catch (AggregateException a)
{
// For demonstration purposes, show the OCE message.
foreach (var v in a.InnerExceptions)
LogtoStatusText("msg: " + v.Message);
}
LogtoStatusText("**** All tasks have been initialized, begin processing *****");
TaskList.ForEach(t => t.Start());
}