0

I have a console program which sends async HTTP requests to an external web API. (HttpClient.GetAsync());)
These tasks can take several minutes to complete - during which I'd like to be able to show to the user that the app is still running - for example by sending Console.WriteLine("I ain't dead - yet") every 10 seconds.

I am not sure how to do it right, without the risk of hiding exceptions, introducing deadlocks etc.

I am aware of the IProgress<T>, however I don't know whether I can introduce it in this case. I am await a single async call which does not report progress. (It's essentially an SDK which calls httpClient GetAsync() method

Also: I cannot set the GUI to 'InProgress', because there is no GUI, its a console app - and it seems to the user as if it stopped working if I don't send an update message every now and then.

Current idea:

            try
            {
                var task = httpClient.GetAsync(uri); //actually this is an SDK method call (which I cannot control and which does not report progress itself)

                while (!task.IsCompleted)
                {
                    await Task.Delay(1000 * 10);
                    this.Logger.Log(Verbosity.Verbose, "Waiting for reply...");
                }
                onSuccessCallback(task.Result);
            }
            catch (Exception ex)
            {
                if (onErrorCallback == null)
                {
                    throw this.Logger.Error(this.GetProperException(ex, caller));
                }
                this.Logger.Log(Verbosity.Error, $"An error when executing command [{action?.Command}] on {typeof(T).Name}", ex);
                onErrorCallback(this.GetProperException(ex, caller));
            }
Bartosz
  • 4,406
  • 7
  • 41
  • 80
  • can't you `await` saying `await action.Action()` – Rahul Nov 05 '18 at 13:16
  • @Rahul - of course, but this way I will wait for 5 minutes with no info for user... – Bartosz Nov 05 '18 at 13:19
  • 2
    What about implementing IProgress instead? https://blogs.msdn.microsoft.com/dotnet/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis/ – Jannik Nov 05 '18 at 13:19
  • 1
    https://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html – Bradley Uffner Nov 05 '18 at 13:21
  • https://stackoverflow.com/questions/20661652/progress-bar-with-httpclient – Christoph Lütjen Nov 05 '18 at 13:22
  • @BradleyUffner - thanks - but I don' think this is possible - I don't have any loop where I can show partial progress. It's just a call to httpClient.GetAsync(), which does not provide any callbacks – Bartosz Nov 05 '18 at 13:23
  • @Fildor - there is no GUI, its a console app - and it seems to the user as if it stopped working if I don't send an update message every now and then. – Bartosz Nov 05 '18 at 13:27
  • Ah, that Info would have helped a lot if you had mentioned it in the question. Anyway. In that case I'd probably use a Timer of some sort that checks the status of the Task instead of a "spin-wait". – Fildor Nov 05 '18 at 13:30
  • @Fildor That isn't a spin wait, that is a Timer. – Aron Nov 05 '18 at 13:31
  • @Aron OK, let me rephrase. I would have implemented the timer differently. But as is, if it works, I don't really see a reason to change it. – Fildor Nov 05 '18 at 13:33
  • @Fildor - yeah, indeed, I've been adding additional info to question as I read the comments:). So... Is there no issue with not 'awaiting' the main task? – Bartosz Nov 05 '18 at 13:34
  • 1
    @Fildor actually I would do the opposite, I would refactor `Timer` to the above code. The reason being is that the above code works correctly in WinForms, WPF and Console, given the above code using the `SynchronizationContext.Current`. – Aron Nov 05 '18 at 13:35
  • @Aron You are right.... that goes into my "neat tricks" notebook. – Fildor Nov 05 '18 at 13:36
  • @Aron - so, are you saying that the solution I have is OK? – Bartosz Nov 05 '18 at 13:38

3 Answers3

5

Let me tidy this code up a bit for you

async Task Main()
{
    var reporter = new ConsoleProgress();
    var result = await WeatherWaxProgressWrapper(() => GetAsync("foo"), reporter);

    Console.WriteLine(result);
}



public async Task<int> GetAsync(string uri)
{
    await Task.Delay(TimeSpan.FromSeconds(10));
    return 1;
}

public async Task<T> WeatherWaxProgressWrapper<T>(Func<Task<T>> method, System.IProgress<string> progress)
{
    var task = method();
    while(!task.IsCompleted && !task.IsCanceled && !task.IsFaulted)
    {
        await Task.WhenAny(task, Task.Delay(1000));
        progress.Report("I ain't dead");
    }
    return await task;
}

public class ConsoleProgress : System.IProgress<string>
{
    public void Report(string value)
    {
        Console.WriteLine(value);
    }
}
Aron
  • 15,464
  • 3
  • 31
  • 64
1

You could have a never-ending Task as a beacon that signals every 10 sec, and cancel it after the completion of the long running I/O operation:

var beaconCts = new CancellationTokenSource();
var beaconTask = Task.Run(async () =>
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromSeconds(10), beaconCts.Token);
        Console.WriteLine("Still going...");
    }
});
await LongRunningOperationAsync();
beaconCts.Cancel();
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
-1

You are looking for System.Progress<T>, a wonderful implementation of IProgress.

https://learn.microsoft.com/en-us/dotnet/api/system.progress-1

You create an object of this class on the "UI thread" or the main thread in your case, and it captures the SynchronizationContext for you. Pass it to your worker thread and every call to Report will be executed on the captured thread, you don't have to worry about anything.

Very useful in WPF or WinForms applications.

Lars
  • 6,421
  • 1
  • 23
  • 24