I have a multi-threaded application where I need to cancel each task after a certain time, even if at the time of cancellation, they use unmanaged resources. Now I use the following code (for example, a console application). In a real application, the delay may occur in the unmanaged resource.
static void Main()
{
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(Do, TaskCreationOptions.LongRunning);
}
Console.ReadLine();
}
private static void Do()
{
new Timer(Thread.CurrentThread.Abort, null, 1000, -1);
try
{
Console.WriteLine("Start " + Task.CurrentId);
Thread.Sleep(2000);
Console.WriteLine("End " + Task.CurrentId);
}
catch (Exception)
{
Console.WriteLine("Thread Aborted " + Task.CurrentId);
}
}
Get the result:
But I'm not sure whether it is right for a real application from the point of view of safety. I also used CancellationToken in different variants, but it not give me correct result because where i use CancellAfter() or .Delay() with timespan and cancel task after ceratain time I got the following results:
static void Main()
{
for (int i = 0; i < 10; i++)
{
var clt = new CancellationTokenSource();
Task task = new Task(() =>
{
Task.Delay(2000).ContinueWith(_ =>
{
clt.Cancel();
}, clt.Token);
Do(clt.Token);
}, clt.Token);
task.Start();
}
Console.ReadLine();
}
private static void Do(CancellationToken cltToken)
{
Console.WriteLine("Start " + Task.CurrentId);
Thread.Sleep(2500);
if (!cltToken.IsCancellationRequested)
{
Console.WriteLine("End " + Task.CurrentId);
}
else
{
Console.WriteLine("Cancelled "+ Task.CurrentId);
}
}
In this situation all task must be cancelled, because Thread.Sleep() > of time allotted to execute each task. But we can see that some of the time to execute.
I also use following construction and give the same result:
static void Main()
{
for (int i = 0; i < 10; i++)
{
var clt = new CancellationTokenSource();
clt.CancelAfter(2000);
Task.Factory.StartNew(Do, clt.Token);
}
Console.ReadLine();
}
private static void Do(object obj)
{
var cltToken = (CancellationToken) obj;
Console.WriteLine("Start " + Task.CurrentId);
Thread.Sleep(2500);
if (!cltToken.IsCancellationRequested)
{
Console.WriteLine("End " + Task.CurrentId);
}
else
{
Console.WriteLine("Cancelled "+ Task.CurrentId);
}
}
I also use Parallel and initialize Cancellation Token Inside method Do(), and use Timer to cancell token after timespan, but all give same result.
SO, Why is this happening and what is the correct way to cancel the task after a certain time???
You can get the same results as your original "abort" version by using the same timings. For example, this code:
static void Main()
{
var clt = new CancellationTokenSource();
clt.CancelAfter(1000);
for (int i = 0; i < 10; i++)
{
Task.Run(() => Do(clt.Token));
}
Console.ReadLine();
}
private static void Do(CancellationToken cltToken)
{
Console.WriteLine("Start " + Task.CurrentId);
Thread.Sleep(2000);
if (!cltToken.IsCancellationRequested)
{
Console.WriteLine("End " + Task.CurrentId);
}
else
{
Console.WriteLine("Cancelled "+ Task.CurrentId);
}
}
Will produce something simliar to:
Start 111
Start 112
Start 113
Start 114
Start 115
Start 116
Start 117
Start 118
Start 119
Start 120
Cancelled 111
Cancelled 112
Cancelled 118
Cancelled 116
Cancelled 114
Cancelled 113
Cancelled 117
Cancelled 115
Cancelled 119
Cancelled 120
Using a CancellationTokenSource
is a better option than aborting threads. Thread.Abort
is a bad idea since it aborts the thread without providing proper cleanup mechanisms. Using the token allows you to cooperatively handle the cancellation in a clean manner.
As for why your other options were not functioning properly - The timings you used were a bit too close together. This is especially an issue when running under the debugger, as it will prevent the timings (ie: CancelAfter
as well as Thread.Sleep
) from firing at the same time. If you run a release build outside of the Visual Studio host process, you'll likely find that they work far more reliably.
First, the issue you are seeing where the cancellation token is not signaled is probably due to subtle timing variations. CancelAfter
should work fine, but you will need to increase the difference between the timeout and the sleep to get a more realistic of picture of what will happen.
Second, and I may be the harbinger of bad news here, but if this unmanaged resource does not offer mechanisms for gracefully terminating an operation then this just got exponentially harder. The reasons are:
- Obviously there is no way to poll the
CancellationToken
while a thread is executing unmanaged code. So there is no way to initiate a gracefully shutdown on your own. - Aside from the fact that you should not be aborting a thread anyway the
Thread.Abort
call will not inject the abort signal into the target until it rejoins the managed realm. In other words, aborts will not terminate threads that are executing unmanaged code. This was done intentionally to make aborts safer.
The only way to make this happen reliably is to run the unmanaged resource out-of-process. That means you will need to spin up a new process to execute the unmanaged code and then use WCF (or other communication protocol) to send messages/data back and forth. If the unmanaged resource does not respond in a timely manner then you can kill the process. This is safe because killing another process does not corrupt the state of the current process.
Hopefully whatever unmanaged resource it is you are using has an API with a gracefully termination mechanism built into it. If it is well written it might have such a feature, but my experience has shown that many do not.
来源:https://stackoverflow.com/questions/19323191/cancel-task-by-time