0

I have a UI application which logs something every second. Every time OpenFileHandler is fired, I must start logging to a new file. This is a very simplified version of the code:

string _logItem;
string _fileName;
CancellationTokenSource _cts;
Task _task;

private void OpenFileHandler(object sender, EventArgs e)
{
    // once OpenFileHandler has been fired, 
    // the _logItem should go to the new file

    if (_task != null && !_task.IsCompleted)
    {
        _cts.Cancel();
        // can't do: _task.Wait();
    }

    if ( _fileName != null )
    {
       _cts = new CancellationTokenSource();
       _task = LogAsync(_fileName, _cts.Token);
    }
}

private async Task LogAsync(string fileName, CancellationToken ct)
{
    using (var writer = new System.IO.StreamWriter(fileName, false))
    {
        try
        {
            while (true)
            {
                await Task.Delay(1000, ct);
                await writer.WriteLineAsync(_logItem);
            }
        }
        finally
        {
            writer.WriteLine("end of log!");
        }
    }
}

The problem: OpenFileHandler is synchronous, but I need to make sure the pending WriteLineAsync has completed and the old log file has been closed, before I can start a new LogAsync task.

I cannot do _task.Wait() inside OpenFileHandler because it will block the UI thread.

I also cannot make OpenFileHandler an async method and do await _task inside it. That's because when the application is closed, and OpenFileHandler is fired with _fileName being null, I want the "end of log!" line to still be there in the log.

How do I solve this?

avo
  • 10,101
  • 13
  • 53
  • 81
  • possible duplicate of [Cancelling a pending task synchronously on the UI thread](http://stackoverflow.com/questions/20876645/cancelling-a-pending-task-synchronously-on-the-ui-thread) – noseratio Mar 13 '14 at 12:18

2 Answers2

3

As the first line of LogAsync, await the old task:

await prevTask;

You somehow need to pass in the previous task. Maybe as a method argument.

You probably need to catch an OperationCancelledException, or:

await prevTask.ContinueWith(_ => { }); //suppress exceptions
usr
  • 168,620
  • 35
  • 240
  • 369
  • This might be it, but how to make sure I still get `end of log!` when the the app exits? There may be no more UI message loop iterations. – avo Mar 13 '14 at 12:06
  • Alternatives: a) keep a message loop running until the last logging task has completed. You don't need UI for a message loop. b) Use `ConfigureAwait(false)` to not take a dependency on the UI at all. You probably want to `Wait` for the logging task as the last line of `Main`. – usr Mar 13 '14 at 12:08
  • The continuation inside `AsyncLog` is queued using `WinFormsSynchronizationContext.Post`, so I have to pump messages for it to be executed when the app exits. `ConfigureAwait(false)` may indeed help, then I could possibly do `Wait()`. In my app `LogAsync` also updates UI, I'd have to use `BeginInvoke` for that if I use `ConfigureAwait(false)`. – avo Mar 13 '14 at 12:17
  • 1
    I'd say that updating the UI from that method is architecturally undesirable anyway. Consider using `IProgress` to notify the UI, or expose an event for the UI to consume. – usr Mar 13 '14 at 13:06
0

Could you not handle the special case of shutting the application down (and null being passed to the OpenFileHandler method)?

So in the case of the application shutting down then you _cts.Cancel() and then _task.Wait() to allow the logs to complete being written.

Rather than doing a while(true) can't you check if the CancellationToken has been cancelled or not, and if it has then exit and write the 'End of Log!'

Sam Holder
  • 32,535
  • 13
  • 101
  • 181
  • I'd loose `end of log!` then. If you do `_task.Wait()` at the shutdown, it will still result in a deadlock – avo Mar 13 '14 at 12:05
  • Check if the CancellationToken - I don't think this would help, if I don't `await` this task after requesting cancellation, this check may not have a chance to execute. – avo Mar 13 '14 at 12:18
  • yes, I'm suggesting that you make the OpenFileHandler async and then Cancel and then Wait for the task when you shut the application down. – Sam Holder Mar 13 '14 at 12:44