C# 中,action.BeginInvoke(action.EndInvoke,null) 是一个好的做法吗?

14

如果我想要执行一段“fire and forget”的代码,但仍希望确保我的内存得到清理(参见为什么异步委托方法需要调用EndInvoke?),下面的代码能够实现这个目标吗?

Action myAction = () => LongRunTime();
myAction.BeginInvoke(myAction.EndInvoke,null);

我搜寻了一下,但没有看到有人使用过那个模式。相反,人们使用匿名方法作为它们的回调(例如The proper way to end a BeginInvoke?),或者他们定义一个实际的回调方法。由于我没有看到其他人这样做,所以我认为它可能不起作用或者是一个不好的主意。

谢谢!


1
我认为使用方法组转换而不是lambda表达式没有任何问题。 - Thorarin
1
如果您能确定这种技术不会导致内存泄漏,那么这将是我要寻找的答案。谢谢! - Matt Klein
2个回答

15

使用方法组转换而不是委托是可以的,EndInvoke 仍然会在你的 Action 上被调用。既定为“发射并忘记”,因此无需执行其他操作。

不幸的是,直接且明确地证明 EndInvoke 已被调用有些困难,因为 Action 是一个委托,我们不能在 BCL 中的某个类上添加断点。

此代码将(定期)检查由 BeginInvoke 返回的 IAsyncResult 的某些私有字段,该字段似乎跟踪了是否已调用 EndInvoke

public partial class MainWindow : Window
{
    private Timer _timer = new Timer(TimerCallback, null, 100, 100);
    private static IAsyncResult _asyncResult;

    public MainWindow()
    {
        InitializeComponent();
    }

    static void LongRunTime()
    {
        Thread.Sleep(1000);
    }

    void Window_Loaded(object sender, RoutedEventArgs args)
    {
        Action myAction = () => LongRunTime();
        _asyncResult = myAction.BeginInvoke(myAction.EndInvoke, null);
    }

    static void TimerCallback(object obj)
    {
        if (_asyncResult != null)
        {
            bool called = ((dynamic)_asyncResult).EndInvokeCalled;
            if (called)
            {
                // Will hit this breakpoint after LongRuntime has completed
                Debugger.Break(); 
                _asyncResult = null;
            }
        }
    }
}

我使用 SOS 进行了双重确认,没有发现任何托管内存泄漏。我还尝试了其他一些证明,但它们更多是间接的,我认为这个证明更为直接。

在我的调查过程中发现了一些有趣的问题:在使用插装进行分析时,myAction.BeginInvoke 调用会显示在性能分析器中,但 myAction.EndInvoke 不会显示。


我看不出这样做有什么意义。直接在任务中调用LongRunTime()就好了。 - Hans Passant
3
任务并不是关键点,但如果您喜欢,这里有一个没有任务的例子。 - Thorarin

2

现在可以这样做:

BeginInvoke((Action)(async () =>
{
    // Show child form
    var f = new MyForm();
    f.ShowDialog();
    // Update parent/current
    await UpdateData();
}));

我认为你把多线程和异步混淆了。这个问题是关于在使用多线程时清理资源的。请查看我在问题中链接的答案以获取上下文。async/await是允许单个线程在后台发生其他事情时继续工作的。 - Matt Klein
此外,在线程池内运行的 lambda 结束时 await UpdateData(); 并不是很有意义。也许你只是忘了在 await 后添加 //do extra work,但按照目前写法可能会让那些不理解何时应该使用 async/await 的人感到困惑。 - Matt Klein

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接