0

I have a piece of code which does some processing of data, written in c#.

string status;
log.Info("Starting the Process");
StartProcessing(); // takes 10-12 mins usually 

var task = Task.Run(() =>
{
    do
    {
        status = GetStatus();
        log.Info("Status inside loop : " + status);
    } while (status != "Complete")
});

if (task.Wait(TimeSpan.FromMinutes(Convert.ToDouble(15))))
{
    log.Info("Processing finished");
}
else
{
    log.Info("Process did not complete in 15 mins");
    log.Info("Stopping the Process");
    StopProcessing(); // this takes 2-3 mins usually.
    log.Info("Process stopped");
}

StartProcessing() method actually start some background processing of the data. It doesn't return any value or wait for the method to finish. So we added a do-while loop to check the status of the processing. If the status is complete , then come of the loop and proceed further.

Now the requirement has changed to put a timeout for the processing. If the processing is taking more than 5 mins, then we have to stop the process. So I have wrapped my code in Task.Run as shown above and written a if else condition to check the time.

This doesn't seem to work as expected, because when I run my code, this is log information I'm getting.

Starting the Process  
Status inside loop : Processing
Status inside loop : Processing
Status inside loop : Processing
Status inside loop : Processing --> this line is repeated multiple times with in 15 mins.  
Process did not complete in 15 mins.  
Stopping the Process  
Status inside loop : Processing  
Status inside loop : Processing  
Status inside loop : Processing --> why is it going back to do while after coming out ?
Process stopped 

The execution is going back to do while even after coming out. Is there anything wrong am I doing here?
Any suggestions are very helpful.

CrazyCoder
  • 2,194
  • 10
  • 44
  • 91
  • Not a direct answer, but you might find this library useful: https://github.com/App-vNext/Polly – jason.kaisersmith Apr 07 '20 at 04:51
  • The problem is that you're only _waiting_ on the task, rather than _cancelling_ it. There are many existing questions with answers on the site that address how to correctly _cancel_ a task. See marked duplicates for a couple. – Peter Duniho Apr 07 '20 at 05:50

2 Answers2

2

You need to add code that terminates your background task after the given time. For this, you best introduce a CancellationToken to your processing task. Do something like:

CancellationTokenSource source = new CancellationTokenSource(TimeSpan.FromMinutes(5));
StartProcessing(source.Token);

and then, within your processing task, regularly call token.IsCancellationRequested and abort if this is true.

PMF
  • 14,535
  • 3
  • 23
  • 49
  • 1
    Good, though you should just call `ThrowIfCancellationRequested` instead. Particularly important if chained tasks/continuations are being used –  Apr 07 '20 at 04:57
  • @PMF , Thanks a lot for the reply. We do not have control over the methods StartProcessing() , GetStatus() , StopProcessing() as these methods are written in a dll and we do not have the source code for it. So I cannot pass any parameters or check anything inside the method. – CrazyCoder Apr 07 '20 at 05:01
  • @Crazy: _"I cannot pass any parameters or check anything inside the method"_ -- then you will have to live with the situation. There's no way to safely interrupt the task without its cooperation. – Peter Duniho Apr 07 '20 at 05:51
  • @PeterDuniho I think the OP's question is more about cancelling the task returned by `Task.Run`, the OP thought, by the looks of the question, after `.Wait(timeout)` returns, the task should automatically stop. – weichch Apr 07 '20 at 06:08
  • @CrazyCoder: Then there's little you can do. You could try to somehow get a handle to the thread that is (likely) created there and kill it, but that's very likely to cause problems. So you should probably ask the creator of that DLL to introduce the possibility to abort the processing. – PMF Apr 07 '20 at 06:34
  • @MickyD I prefer to cancel my tasks without exceptions being thrown, but that depends on the context. – PMF Apr 07 '20 at 06:36
  • @weichch: _"I think the OP's question is more about cancelling the task returned by Task.Run"_ -- of course it is. And they can't do that without the cooperation of the task's implementation itself. – Peter Duniho Apr 07 '20 at 06:38
0

If you have access to source code of StartProcess, then like @PMF said, passing CancellationToken to the method is the efficient way.

Otherwise, you can create your own cancelable task using TaskCompletionSource and CancellationTokenSource:

Task<bool> ProcessAsync(TimeSpan timeout)
{
    var tcs = new TaskCompletionSource<bool>();
    var cts = new CancellationTokenSource();

    // If task canceled, set to return false
    cts.Token.Register(() => tcs.TrySetResult(false));

    // Cancel after timeout
    cts.CancelAfter(timeout);

    // Start process.
    StartProcess();

    // Start waiting until complete or canceled.
    Task.Run(() =>
    {
        while (GetStatus() != "Complete" && !cts.IsCancellationRequested)
        {
        }

        if (cts.IsCancellationRequested)
        {
            // Task has been canceled due to timeout.
            tcs.TrySetResult(false);
        }
        else
        {
            // Task done. Status is "Completed".
            tcs.TrySetResult(true);
        }
    });

    return tcs.Task;
}

Then await ProcessAsync:

// Or ProcessAsync(TimeSpan.FromMinutes(5)).Result for sync method
var isCompleted = await ProcessAsync(TimeSpan.FromMinutes(5));
if (!isCompleted)
{
    StopProcess();
}
weichch
  • 9,306
  • 1
  • 13
  • 25