显示IProgress并支持取消WinForms异步任务的模式进度表单 [英] Modal Progress Form showing IProgress and supporting Cancellation of async Task for WinForms

查看:123
本文介绍了显示IProgress并支持取消WinForms异步任务的模式进度表单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直试图有一个可重用的模式进度窗口(即progressForm.ShowDialog()),以显示正在运行的异步任务的进度,包括启用取消。

我已经看到了一些实现通过将激活事件处理程序挂在窗体上来启动异步任务,但我需要先启动任务,然后显示将显示进度的模式对话框,然后在完成或取消完成时关闭模式对话框(请注意-我希望取消操作完成时关闭表单-表示从任务继续关闭。)

I have been attempting to have a re-usable modal progress window (I.e. progressForm.ShowDialog()) to show progress from a running async task, including enabling cancellation.
I have seen some implementations that launch start the async task by hooking the Activated event handler on the form, but I need to start the task first, then show the modal dialog that will show it's progress, and then have the modal dialog close when completed or cancellation is completed (note - I want the form closed when cancellation is completed - signalled to close from the task continuation).

我目前有以下内容-尽管可以正常工作-是否存在问题这-还是可以采用更好的方法呢?

I currently have the following - and although this working - are there issues with this - or could this be done in a better way?

我确实读到我需要运行此CTRL-F5,而不进行调试(以避免AggregateException停止调试器)在继续中-并使其像生产代码中一样被放入try catch中)

I did read that I need to run this CTRL-F5, without debugging (to avoid the AggregateException stopping the debugger in the continuation - and let it be caught in the try catch as in production code)

ProgressForm.cs
-对于m,带有ProgressBar(progressBar1)和Button(btnCancel)

ProgressForm.cs - Form with ProgressBar (progressBar1) and Button (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;
    }
}

Services.cs
-包含以下内容的类文件WinForms应用程序(以及控制台应用程序)消耗的逻辑

Services.cs - Class file containing logic consumed by WinForms app (as well as console app)

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

MainForm.cs
-带按钮的表单(btnDo)。

MainForm.cs - Form with Button (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);
    }


推荐答案


我认为我遇到的最大问题是,使用await(而不是
ContinueWith)意味着我无法使用ShowDialog,因为它们都阻塞了
调用。如果我先调用ShowDialog,则代码将在此时被阻塞,
和进度表需要实际启动async方法(
是我要​​避免的方法)。如果我先调用await
MyService.DoSomethingWithResultAsync,则此阻止,然后我
无法显示我的进度表。

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 / await TaskCompletionSource 利用这种行为:

The ShowDialog is indeed a blocking API in the sense it doesn't return until the dialog has been closed. But it is non-blocking in the sense it continues to pump messages, albeit on a new nested message loop. We can utilize this behavior with async/await and TaskCompletionSource:

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

这篇关于显示IProgress并支持取消WinForms异步任务的模式进度表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆