Begininvoke Paired With Endinvoke

ID

csharp.begininvoke_paired_with_endinvoke

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

CSharp

Tags

async, code_smell, delegate, resource-leak

Description

Reports a delegate BeginInvoke that no EndInvoke completes, in the two cases that can be decided at the call site: the callback argument is null and no EndInvoke appears in the enclosing method, or the callback is written inline as a one-parameter lambda or anonymous method whose body never calls EndInvoke.

A callback passed as a method group names a body this rule cannot see, and is not reported. Neither is the unrelated BeginInvoke that marshals a call onto a UI thread, whose argument list does not have the callback, state tail of a delegate.

Rationale

BeginInvoke starts the call on a pool thread and returns an IAsyncResult that owns real resources — a wait handle among them. The asynchronous call is not finished until EndInvoke is called on that result.

EndInvoke is what releases those resources, what returns the value along with any out and ref arguments, and what rethrows an exception raised inside the delegate. Skip it and the exception is swallowed with no trace anywhere, the result is never collected, and the handle is held until finalization. The contract for this pattern is that each BeginInvoke is matched by exactly one EndInvoke, with no exception for a call whose result nobody wants.

using System;

public delegate int Compute(int value);

public class Runner
{
    private Compute compute;

    public void FireAndForget()
    {
        compute.BeginInvoke(1, null, null);                          // FLAW, nothing ever ends the call
    }

    public void WithCallback()
    {
        compute.BeginInvoke(1, ar => Log("done"), null);             // FLAW, the callback does not end it
    }

    public void Blocking()
    {
        IAsyncResult result = compute.BeginInvoke(1, null, null);    // OK, ended below
        int value = compute.EndInvoke(result);
        Log(value.ToString());
    }

    public void Completed()
    {
        compute.BeginInvoke(1, ar => compute.EndInvoke(ar), null);   // OK, ended in the callback
    }

    private void Log(string message) { }
}

Remediation

Call EndInvoke exactly once for every BeginInvoke, on the IAsyncResult that BeginInvoke returned: inside the callback when there is one, otherwise at the point where the result is needed.

Prefer not to write the pattern at all in new code. A task-returning method awaited by the caller expresses the same intent, propagates exceptions where they can be caught, and has nothing to pair up.