通知一个长 运行 异步任务正在进行中——正确的方法
Inform that a long running async task is in progress - the right way
我有一个控制台程序可以向外部网站发送异步 HTTP 请求 API。 (HttpClient.GetAsync());
)
这些任务可能需要几分钟才能完成 - 在此期间我希望能够向用户显示该应用程序仍然 运行 - 例如每 10 秒发送一次 Console.WriteLine("I ain't dead - yet")
。
我不确定如何正确地做到这一点,而不会有隐藏异常、引入死锁等的风险。
我知道IProgress,但是不知道能不能在这种情况下引入。我正在等待一个不报告进度的异步调用。 (它本质上是一个调用 httpClient GetAsync() 方法的 SDK
还有:
我无法将 GUI 设置为 'InProgress',因为没有 GUI,它是一个控制台应用程序 - 如果我不时不时地发送更新消息,用户就好像它停止工作了。
当前想法:
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));
}
让我为您稍微整理一下这段代码
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);
}
}
您正在寻找 System.Progress<T>
,IProgress
的精彩实现。
https://docs.microsoft.com/en-us/dotnet/api/system.progress-1
您在 "UI thread" 或您的主线程上创建此 class 的对象,它会为您捕获 SynchronizationContext
。将它传递给你的工作线程,每次调用 Report
都会在捕获的线程上执行,你不必担心任何事情。
在 WPF 或 WinForms 应用程序中非常有用。
你可以有一个永无止境的 Task
作为信标,每 10 秒发出一次信号,并在长时间的 运行 I/O 操作完成后取消它:
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();
我有一个控制台程序可以向外部网站发送异步 HTTP 请求 API。 (HttpClient.GetAsync());
)
这些任务可能需要几分钟才能完成 - 在此期间我希望能够向用户显示该应用程序仍然 运行 - 例如每 10 秒发送一次 Console.WriteLine("I ain't dead - yet")
。
我不确定如何正确地做到这一点,而不会有隐藏异常、引入死锁等的风险。
我知道IProgress
还有: 我无法将 GUI 设置为 'InProgress',因为没有 GUI,它是一个控制台应用程序 - 如果我不时不时地发送更新消息,用户就好像它停止工作了。
当前想法:
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));
}
让我为您稍微整理一下这段代码
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);
}
}
您正在寻找 System.Progress<T>
,IProgress
的精彩实现。
https://docs.microsoft.com/en-us/dotnet/api/system.progress-1
您在 "UI thread" 或您的主线程上创建此 class 的对象,它会为您捕获 SynchronizationContext
。将它传递给你的工作线程,每次调用 Report
都会在捕获的线程上执行,你不必担心任何事情。
在 WPF 或 WinForms 应用程序中非常有用。
你可以有一个永无止境的 Task
作为信标,每 10 秒发出一次信号,并在长时间的 运行 I/O 操作完成后取消它:
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();