OK, I think this makes things clearer.
Good, my turn to confuse matters (i.e. create a mess that Jon has to clean
up
).
Calling BeginInvoke on an "ordinary" delegate, without calling
EndInvoke, can cause resource usages to hang around longer than
necessary (until the next garbage collection), which can cause temporary
starvation in a server or other victim when a heavy user of resources is
present. However, there's no permanent leak.
Calling BeginInvoke on Control, without calling EndInvoke, doesn't even
have that problem.
The first paragraph is correct for sure. I remain unconvinced that it's
untrue for Control.BeginInvoke(), at least as an unqualified statement.
Consider the statement in the doc page for IAsyncResult.AsyncWaitHandle:
Notes to Implementers The object that implements IAsyncResult
does not need to create the WaitHandle until the AsyncWaitHandle
property is read. It is the choice of the IAsyncResult
implementer. However, if the implementer creates AsyncWaitHandle,
it is the responsibility of the implementer to signal the
WaitHandle that will terminate the wait at the appropriate time.
For example, System.Runtime.Remoting.Messaging.AsyncResult
terminates the wait on behalf of the caller when an asynchronously
invoked method returns. Once created, AsyncWaitHandle should be
kept alive until the user calls the method that concludes the
asynchronous operation. At that time the object behind
AsyncWaitHandle can be discarded.
In other words, if the caller to Control.BeginInvoke() actually uses the
AsyncWaitHandle from the IAsyncResult it gets from Control.BeginInvoke(),
then that WaitHandle (which implements IDisposable and so must be disposed
when it's no longer needed) is required to be kept until the "_user_
[emphasis mine] calls the method that concludes the asynchronous
operation". In other words, the user's code has every reason to expect
that the WaitHandle will remain valid until that code calls
Control.EndInvoke().
So code that gets the AsyncWaitHandle is going to have to call EndInvoke()
to ensure they clean up that resource properly (EndInvoke() baing "the
method that concludes the asynchronous operation".
Beyond that, even if you can show in the current Control implementation
that there's no harm in not calling Control.EndInvoke(), I would say that
absent some clear, explicit statement in the MSDN documentation pages that
says that you don't need to call EndInvoke(), that pairing the two calls
may well be important, and very much _should_ be assumed to be so, at
least if your code uses the AsyncWaitHandle in the IAsyncResult, if not in
other cases.
I've looked for such a clear, explicit statement and haven't been able to
find one.
I'm carefully ignoring the fact that Control.BeginInvoke() doesn't follow
the normal pattern for async methods. There's no way to specify a
callback, never mind a context/state object for the callback. This means
it's practically impossible for the asynchronously invoked method to have
access to the IAsyncResult (it's not literally impossible, but it's
impractical because any attempt to provide access results in a race
condition...that can be addressed by adding thread synchronization, but
then that seems to me to negate the whole point of using BeginInvoke() in
the first place).
This means that if you _do_ call EndInvoke() you have to do it somewhere
other than the invoked delegate. This leads to inelegant things like
polling IAsyncResult.IsCompleted, or just calling EndInvoke() and possibly
getting blocked anyway. I suppose there are situations where this is
actually what you'd want to do, but being forced into seems kind of ugly
to me.
In other words, it bugs me that the whole Control.BeginInvoke() API is the
way it is. While I can understand the need for backward compatibility, I
don't understand why it remains in this sort of awkward state rather than
adding some overloads to Control.BeginInvoke() to allow it to be used in a
more typical fashion.
There are 8,192 executions of BeginInvoke in the situation I'm presently
concerned with, so I think that temporary starvation isn't happening,
and nothing worse can happen.
I suspect that as long as you do nothing with the IAsyncResult returned by
BeginInvoke(), you're fine. But I wish there was some more explicit
documentation to that effect.
I still have some curiosity remaining from a former not-having-a-life as
a language lawyer. When I create a delegate that points to a private
method in a form, the form inherits from Control. But how is that
enough to make the delegate know whether to be a Control.something (with
method Control.BeginInvoke) or an "ordinary" delegate (with method
somethingelse.BeginInvoke)?
One feature of delegates is that they are not specific to whatever method
they might be passed to. You can create a single delegate that is
suitable for either Control.BeginInvoke() or Delegate.BeginInvoke() (and
for the synchronous Invoke() equivalents as well). The delegate does not
need to be related to the Control class at all, never mind be an instance
member, or a member of any Control-derived class of any sort.
The corollary is that the delegate itself doesn't "know whether to be"
anything in particular, nor does it need to. The sole determining factor
is how you use it If you call Control.BeginInvoke(), then it's used by
Control.BeginInvoke(), executed on that Control instance's owning thread.
If you call Delegate.BeginInvoke() on the delegate instance itself, then
it's invoked asynchronously via that method.
Pete