.NET异步关闭方法? [英] .NET Async in shutdown methods?

查看:40
本文介绍了.NET异步关闭方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用异步方法连接到REST API的应用程序.我几乎在所有连接到API的地方都使用async/await来进行此设置,但是我有一个问题和一些我不完全理解的奇怪行为.我想要做的就是在程序关闭时仅在某些情况下返回许可证.这是由关闭窗口事件启动的;事件处理程序如下:

I have an application that connects to a REST API using async methods. I have this set up using async/await pretty much everywhere that connects to the API, however I have a question and some strange behavior that I don't completely understand. What I want to do is simply return a license in certain scenarios when the program shuts down. This is initiated by a window closing event; the event handler is as follows:

async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            ...other synchronous code...

            //Check for floating licensing
            if (KMApplication.License != null && KMApplication.License.Scope != Enums.LicenseScope.Standalone)
            {
                for (int i = 0; i < 3; i++)
                {
                    try
                    {

                        await KMApplication.License.ShutDown(KMApplication.Settings == null
                                                                 ? Enums.LicenseReturnModes.PromptOnShutdown
                                                                 : KMApplication.Settings.LicenseReturnMode)
                                           .ConfigureAwait(false);
                        break;
                    }
                    catch (Exception ex)
                    {
                        _logger.Warn("Exception in license release, attempt " + i, ex);
                    }
                }
            }

            await KMApplication.ApiService.Disconnect().ConfigureAwait(false);

            _logger.Info("Shutdown Complete");

            Application.Current?.Shutdown();
        }

运行此命令后,我可以逐步进入调试器,并转到第一个许可证关闭调用,这是第一个异步等待的调用.然后,当我按F10键进入下一行代码时,它只是关闭并消失了.我验证了应该在该行中进行的许可证发布正在进行中,因此它似乎可以运行到该行的完成,但是随后关闭或崩溃或发生其他情况.我还查看了日志,但它从未进入 Shutdown Complete 行,而且我也不认为它也进入了 ApiService.Disconnect .

When this runs I can step through in the debugger and it gets to the first license shutdown call which is the first async awaited call. Then when I press F10 to step to the next line of code it just shuts down and is gone. I verified that the license release that is supposed to be happening in that line is in face happening so it appears to run to completion of that line but then shuts down or crashes or something. I also looked at the logs and it never gets to the Shutdown Complete line and I don't believe it's getting to the ApiService.Disconnect either.

我还尝试使用 Task.Run(()=> ...该方法...).GetAwaiter().GetResult()作为同步方法运行此方法,但这只是死锁在第一个电话上.

I also tried running this as a sync method using Task.Run(() => ...the method...).GetAwaiter().GetResult() but that just deadlocks on the first call.

我该如何处理并使其运行异步发行版,等待其完成,然后关闭?

How do I handle this and have it run the async release, wait for it to be done, then shut down?

推荐答案

此处是

Here is an async version of the FormClosing event. It delays the closing of the form until the completion of the supplied Task. The user is prevented from closing the form before the completion of the task.

OnFormClosingAsync 事件传递了处理程序代码的 FormClosingEventArgs 类,具有两个附加属性: bool HideForm int Timeout .这些属性是读/写的,很像现有的 取消 属性.将 HideForm 设置为 true 的作用是在进行异步操作时隐藏表单,以避免使用户感到沮丧.将 Timeout 设置为大于0的值的作用是,在指定的持续时间(以毫秒为单位)之后放弃异步操作,并关闭表单.否则,有可能使用隐藏的UI无限期地运行该应用程序,这肯定是一个问题. Cancel 属性仍然可用,并且可以通过事件处理程序设置为 true ,以防止表单关闭.

The OnFormClosingAsync event passes an enhanced version of the FormClosingEventArgs class to the handling code, with two additional properties: bool HideForm and int Timeout. These properties are read/write, much like the existing Cancel property. Setting HideForm to true has the effect of hiding the form while the async operation is in progress, to avoid frustrating the user. Setting Timeout to a value > 0 has the effect of abandoning the async operation after the specified duration in msec, and closing the form. Otherwise it is possible that the application could be left running indefinitely with a hidden UI, which could certainly be a problem. The Cancel property is still usable, and can be set to true by the handler of the event, to prevent the form from closing.

static class WindowsFormsAsyncExtensions
{
    public static IDisposable OnFormClosingAsync(this Form form,
        Func<object, FormClosingAsyncEventArgs, Task> handler)
    {
        Task compositeTask = null;
        form.FormClosing += OnFormClosing; // Subscribe to the event
        return new Disposer(() => form.FormClosing -= OnFormClosing);

        async void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            if (compositeTask != null)
            {
                // Prevent the form from closing before the task is completed
                if (!compositeTask.IsCompleted) { e.Cancel = true; return; }
                // In case of success allow the form to close
                if (compositeTask.Status == TaskStatus.RanToCompletion) return;
                // Otherwise retry calling the handler
            }
            e.Cancel = true; // Cancel the normal closing of the form
            var asyncArgs = new FormClosingAsyncEventArgs(e.CloseReason);
            var handlerTask = await Task.Factory.StartNew(
                () => handler(sender, asyncArgs),
                CancellationToken.None, TaskCreationOptions.DenyChildAttach,
                TaskScheduler.Default); // Start in a thread-pool thread
            var hideForm = asyncArgs.HideForm;
            var timeout = asyncArgs.Timeout;
            if (hideForm) form.Visible = false;
            compositeTask = Task.WhenAny(handlerTask, Task.Delay(timeout)).Unwrap();
            try
            {
                await compositeTask; // Await and then continue in the UI thread
            }
            catch (OperationCanceledException) // Treat this as Cancel = true
            {
                if (hideForm) form.Visible = true;
                return;
            }
            catch // On error don't leave the form hidden
            {
                if (hideForm) form.Visible = true;
                throw;
            }
            if (asyncArgs.Cancel) // The caller requested to cancel the form close
            {
                compositeTask = null; // Forget the completed task
                if (hideForm) form.Visible = true;
                return;
            }
            await Task.Yield(); // Ensure that form.Close will run asynchronously
            form.Close(); // Finally close the form
        }
    }

    private struct Disposer : IDisposable
    {
        private readonly Action _action;
        public Disposer(Action disposeAction) => _action = disposeAction;
        void IDisposable.Dispose() => _action?.Invoke();
    }
}

public class FormClosingAsyncEventArgs : EventArgs
{
    public CloseReason CloseReason { get; }
    private volatile bool _cancel;
    public bool Cancel { get => _cancel; set => _cancel = value; }
    private volatile bool _hideForm;
    public bool HideForm { get => _hideForm; set => _hideForm = value; }
    private volatile int _timeout;
    public int Timeout { get => _timeout; set => _timeout = value; }

    public FormClosingAsyncEventArgs(CloseReason closeReason) : base()
    {
        this.CloseReason = closeReason;
        this.Timeout = System.Threading.Timeout.Infinite;
    }
}

由于 OnFormClosingAsync 是扩展方法,而不是实际事件,因此它只能有一个处理程序.

Since OnFormClosingAsync is an extension method and not a real event, it can only have a single handler.

用法示例:

public Form1()
{
    InitializeComponent();
    this.OnFormClosingAsync(Window_FormClosingAsync);
}

async Task Window_FormClosingAsync(object sender, FormClosingAsyncEventArgs e)
{
    e.HideForm = true; // Optional
    e.Timeout = 5000; // Optional
    await KMApplication.License.ShutDown();
    //e.Cancel = true; // Optional
}

Window_FormClosingAsync 处理程序将在线程池线程中运行,因此它不应包含任何UI操作代码.

The Window_FormClosingAsync handler will run in a thread-pool thread, so it should not include any UI manipulation code.

通过保留对 IDisposable 返回值的引用并进行处理,可以取消订阅该事件.

Unsubscribing from the event is possible, by keeping a reference of the IDisposable return value, and disposing it.

更新:阅读这个答案,我意识到可以在表单中添加真实事件 FormClosingAsync ,而无需创建从表单继承的类.这可以通过添加事件,然后运行将事件挂钩到本机 FormClosing 事件的初始化方法来实现.像这样:

Update: After reading this answer, I realized that it is possible to add a real event FormClosingAsync in the form, without creating a class that inherits from the form. This can be achieved by adding the event, and then running an initialization method that hooks the event to the native FormClosing event. Something like this:

public event Func<object, FormClosingAsyncEventArgs, Task> FormClosingAsync;

public Form1()
{
    InitializeComponent();
    this.InitFormClosingAsync(() => FormClosingAsync);

    this.FormClosingAsync += Window_FormClosingAsync_A;
    this.FormClosingAsync += Window_FormClosingAsync_B;
}

在初始化程序内部,在本机 FormClosing 事件的内部处理程序中,可以检索该事件的所有订阅者使用 GetInvocationList 方法:

Inside the initializer, in the internal handler of the native FormClosing event, all the subscribers of the event can be retrieved using the GetInvocationList method:

var eventDelegate = handlerGetter();
if (eventDelegate == null) return;
var invocationList = eventDelegate.GetInvocationList()
    .Cast<Func<object, FormClosingAsyncEventArgs, Task>>().ToArray();

...然后适当地调用.所有这些都增加了复杂性,同时还讨论了允许多个处理程序的有用性.因此,我可能会坚持使用原始的单处理程序设计.

...and then invoked appropriately. All this adds complexity, while the usefulness of allowing multiple handlers is debated. So I would probably stick with the original single-handler design.

更新:仍然可以使用原始方法 OnFormClosingAsync 使用多个处理程序.实际上,这很容易. Func< T> 类继承自 Delegate ,因此它具有像真实事件一样的调用列表:

Update: It is still possible to have multiple handlers using the original method OnFormClosingAsync. It is quite easy actually. The Func<T> class inherits from Delegate, so it has invocation list like a real event:

Func<object, FormClosingAsyncEventArgs, Task> aggregator = null;
aggregator += Window_FormClosingAsync_A;
aggregator += Window_FormClosingAsync_B;
this.OnFormClosingAsync(aggregator);

不需要在 OnFormClosingAsync 方法中进行修改.

No modification in the OnFormClosingAsync method is required.

这篇关于.NET异步关闭方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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