gpt4 book ai didi

c# - 模态进度表显示 IProgress 并支持取消 WinForms 的异步任务

转载 作者:太空狗 更新时间:2023-10-29 21:39:25 26 4
gpt4 key购买 nike

我一直在尝试创建一个可重复使用的模式进度窗口(即 progressForm.ShowDialog())来显示正在运行的异步任务的进度,包括启用取消。
我已经看到一些实现通过 Hook 表单上的 Activated 事件处理程序来启动异步任务,但我需要先启动任务,然后显示将显示其进度的模态对话框,然后在完成后关闭模态对话框或取消已完成(注意 - 我希望在取消完成时关闭表单 - 从任务继续发出关闭信号)。

我目前有以下内容 - 虽然可以正常工作 - 是否存在问题 - 或者可以用更好的方式完成吗?

我确实读到我需要运行这个 CTRL-F5,而不进行调试(以避免 AggregateException 在继续中停止调试器 - 并让它像在生产代码中一样在 try catch 中被捕获)

进度表.cs- 带有进度条 (progressBar1) 和按钮 (btnCancel) 的表单

public partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}

public event Action Cancelled;
private void btnCancel_Click(object sender, EventArgs e)
{
if (Cancelled != null) Cancelled();
}

public void UpdateProgress(int progressInfo)
{
this.progressBar1.Value = progressInfo;
}
}

服务.cs- 包含 WinForms 应用程序(以及控制台应用程序)使用的逻辑的类文件

public class MyService
{
public async Task<bool> DoSomethingWithResult(
int arg, CancellationToken token, IProgress<int> progress)
{
// Note: arg value would normally be an
// object with meaningful input args (Request)

// un-quote this to test exception occuring.
//throw new Exception("Something bad happened.");

// Procressing would normally be several Async calls, such as ...
// reading a file (e.g. await ReadAsync)
// Then processing it (CPU instensive, await Task.Run),
// and then updating a database (await UpdateAsync)
// Just using Delay here to provide sample,
// using arg as delay, doing that 100 times.

for (int i = 0; i < 100; i++)
{
token.ThrowIfCancellationRequested();
await Task.Delay(arg);
progress.Report(i + 1);
}

// return value would be an object with meaningful results (Response)
return true;
}
}

主窗体.cs- 带按钮的表单 (btnDo)。

public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}

private async void btnDo_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// Create the ProgressForm, and hook up the cancellation to it.
ProgressForm progressForm = new ProgressForm();
progressForm.Cancelled += () => cts.Cancel();

// Create the progress reporter - and have it update
// the form directly (if form is valid (not disposed))
Action<int> progressHandlerAction = (progressInfo) =>
{
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.UpdateProgress(progressInfo);
};
Progress<int> progress = new Progress<int>(progressHandlerAction);

// start the task, and continue back on UI thread to close ProgressForm
Task<bool> responseTask
= MyService.DoSomethingWithResultAsync(100, token, progress)
.ContinueWith(p =>
{
if (!progressForm.IsDisposed) // don't attempt to close disposed form
progressForm.Close();
return p.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());

Debug.WriteLine("Before ShowDialog");

// only show progressForm if
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.ShowDialog();

Debug.WriteLine("After ShowDialog");

bool response = false;

// await for the task to complete, get the response,
// and check for cancellation and exceptions
try
{
response = await responseTask;
MessageBox.Show("Result = " + response.ToString());
}
catch (AggregateException ae)
{
if (ae.InnerException is OperationCanceledException)
Debug.WriteLine("Cancelled");
else
{
StringBuilder sb = new StringBuilder();
foreach (var ie in ae.InnerExceptions)
{
sb.AppendLine(ie.Message);
}
MessageBox.Show(sb.ToString());
}
}
finally
{
// Do I need to double check the form is closed?
if (!progressForm.IsDisposed)
progressForm.Close();
}

}
}

修改后的代码 - 按照建议使用 TaskCompletionSource...

    private async void btnDo_Click(object sender, EventArgs e)
{
bool? response = null;
string errorMessage = null;
using (CancellationTokenSource cts = new CancellationTokenSource())
{
using (ProgressForm2 progressForm = new ProgressForm2())
{
progressForm.Cancelled +=
() => cts.Cancel();
var dialogReadyTcs = new TaskCompletionSource<object>();
progressForm.Shown +=
(sX, eX) => dialogReadyTcs.TrySetResult(null);
var dialogTask = Task.Factory.StartNew(
() =>progressForm.ShowDialog(this),
cts.Token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
await dialogReadyTcs.Task;
Progress<int> progress = new Progress<int>(
(progressInfo) => progressForm.UpdateProgress(progressInfo));
try
{
response = await MyService.DoSomethingWithResultAsync(50, cts.Token, progress);
}
catch (OperationCanceledException) { } // Cancelled
catch (Exception ex)
{
errorMessage = ex.Message;
}
finally
{
progressForm.Close();
}
await dialogTask;
}
}
if (response != null) // Success - have valid response
MessageBox.Show("MainForm: Result = " + response.ToString());
else // Faulted
if (errorMessage != null) MessageBox.Show(errorMessage);
}

最佳答案

I think the biggest issue I have, is that using await (instead of ContinueWith) means I can't use ShowDialog because both are blocking calls. If I call ShowDialog first the code is blocked at that point, and the progress form needs to actually start the async method (which is what I want to avoid). If I call await MyService.DoSomethingWithResultAsync first, then this blocks and I can't then show my progress form.

ShowDialog 实际上是一个阻塞 API,因为它在对话框关闭之前不会返回。但它在继续泵送消息的意义上是非阻塞的,尽管是在一个新的嵌套消息循环中。我们可以利用 async/awaitTaskCompletionSource 的这种行为:

private async void btnDo_Click(object sender, EventArgs e)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// Create the ProgressForm, and hook up the cancellation to it.
ProgressForm progressForm = new ProgressForm();
progressForm.Cancelled += () => cts.Cancel();

var dialogReadyTcs = new TaskCompletionSource<object>();
progressForm.Load += (sX, eX) => dialogReadyTcs.TrySetResult(true);

// show the dialog asynchronousy
var dialogTask = Task.Factory.StartNew(
() => progressForm.ShowDialog(),
token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());

// await to make sure the dialog is ready
await dialogReadyTcs.Task;

// continue on a new nested message loop,
// which has been started by progressForm.ShowDialog()

// Create the progress reporter - and have it update
// the form directly (if form is valid (not disposed))
Action<int> progressHandlerAction = (progressInfo) =>
{
if (!progressForm.IsDisposed) // don't attempt to use disposed form
progressForm.UpdateProgress(progressInfo);
};
Progress<int> progress = new Progress<int>(progressHandlerAction);

try
{
// await the worker task
var taskResult = await MyService.DoSomethingWithResultAsync(100, token, progress);
}
catch (Exception ex)
{
while (ex is AggregateException)
ex = ex.InnerException;
if (!(ex is OperationCanceledException))
MessageBox.Show(ex.Message); // report the error
}

if (!progressForm.IsDisposed && progressForm.Visible)
progressForm.Close();

// this make sure showDialog returns and the nested message loop is over
await dialogTask;
}

关于c# - 模态进度表显示 IProgress 并支持取消 WinForms 的异步任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22187668/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com