Bartosz
Bartosz

Reputation: 4766

Inform that a long running async task is in progress - the right way

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));
            }

Upvotes: 0

Views: 2340

Answers (3)

Theodor Zoulias
Theodor Zoulias

Reputation: 43464

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();

Upvotes: 1

Lars
Lars

Reputation: 6529

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.

Upvotes: -1

Aron
Aron

Reputation: 15772

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);
    }
}

Upvotes: 5

Related Questions