C# Is action.BeginInvoke(action.EndInvoke,null) a good idea?
Asked Answered
D

2

14

If I want to do a "fire and forget" of some code, but still want to ensure that my memory is cleaned up (per Why does asynchronous delegate method require calling EndInvoke?), will the following achieve that goal?

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

I've looked around but haven't seen that pattern used anywhere. Rather, people use an annonomoyus method as their callback (such as The proper way to end a BeginInvoke?) or they define an actual callback method. Since I haven't seen anyone else do this, it makes me think it either doesn't work or is otherwise a bad idea.

Thanks!

Decaliter answered 12/4, 2013 at 9:23 Comment(2)
I don't see any problem with using a method group conversion over a lambda expression.Depside
If you can add an answer one way or the other if this technique will not cause a memory leak then that would be the answer that I'm looking for. Thanks!Decaliter
D
15

Using a method group conversion instead of a delegate is fine, the EndInvoke will still be called in on your Action. There is nothing else to be done, since this is a fire and forget call.

Unfortunately, it's somewhat hard to directly irrefutably prove that EndInvoke is called, since Action is a delegate and we can't just add a breakpoint on some class in the BCL.

This code will (periodically) inspect some private field of the IAsyncResult that is returned by BeginInvoke, which seems to keep track of whether or not EndInvoke has been called yet:

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

I've double checked using SOS that there aren't any managed memory leaks. I've also tried several other proofs, but they were more circumstantial than this one, I think.

Some interesting I discovered during my investigation: the myAction.BeginInvoke call will show up on profilers using instrumentation, but myAction.EndInvoke does not.

Depside answered 12/4, 2013 at 22:21 Comment(2)
There's no point to this that I can see. Just call LongRunTime() directly inside the task.Denims
The Task was besides the point, but here's an example without a Task if you prefer.Depside
S
2

Nowdays it could be done like

BeginInvoke((Action)(async () =>
{
    // Show child form
    var f = new MyForm();
    f.ShowDialog();
    // Update parent/current
    await UpdateData();
}));
Scrag answered 30/3, 2018 at 4:47 Comment(2)
I think you're confusing multi-threaded with asynchronous. The question was about cleaning up resources when using multiple threads. Please look at the answer to the question I linked to in my question for context. async/await is about allowing a single thread to continue working while something else is happening in the background.Decaliter
Also, it doesn't really make sense to await UpdateData(); at the end of a lambda that is running in the thread pool. Maybe you are just missing //do extra work after the await, but as it's written it may confuse people who don't understand when you would want to use async/await.Decaliter

© 2022 - 2024 — McMap. All rights reserved.