July 24th, 2023

Why does IAsyncAction or IAsyncOperation.GetResults() produce a E_ILLEGAL_METHOD_CALL exception?

For expository purposes, let’s look at the code we wrote some time ago which obtains network usage information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Networking.Connectivity;

class Program
{
    static async Task DoIt()
    {
        var now = DateTime.Now;
        var states = new NetworkUsageStates
        { Roaming = TriStates.DoNotCare, Shared = TriStates.DoNotCare };

        var profiles = NetworkInformation.GetConnectionProfiles();
        foreach (var profile in profiles)
        {
            var usages = await profile.GetNetworkUsageAsync(
                now.AddDays(-1), now, DataUsageGranularity.PerDay,
                states);
            var usage = usages[0];
            if (usage.ConnectionDuration > TimeSpan.Zero)
            {
                Console.WriteLine(profile.ProfileName);
                Console.WriteLine($"BytesReceived = {usage.BytesReceived}");
                Console.WriteLine($"BytesSent = {usage.BytesSent}");
                Console.WriteLine($"ConnectionDuration = {usage.ConnectionDuration}");
                Console.WriteLine($"------------------");
            }
        }

    }
    static void Main()
    {
        DoIt().GetAwaiter().GetResult();
    }
}

But instead of wrapping the calculations inside a DoIt helper method, why not just grab the results directly?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Networking.Connectivity;

[STAThread]
static void Main()
{
    var now = DateTime.Now;
    var states = new NetworkUsageStates
    { Roaming = TriStates.DoNotCare, Shared = TriStates.DoNotCare };

    var profiles = NetworkInformation.GetConnectionProfiles();
    foreach (var profile in profiles)
    {
        var usages = /* await */ profile.GetNetworkUsageAsync(
            now.AddDays(-1), now, DataUsageGranularity.PerDay,
            states).GetResults();
        var usage = usages[0];
        if (usage.ConnectionDuration > TimeSpan.Zero)
        {
            Console.WriteLine(profile.ProfileName);
            Console.WriteLine($"BytesReceived = {usage.BytesReceived}");
            Console.WriteLine($"BytesSent = {usage.BytesSent}");
            Console.WriteLine($"ConnectionDuration = {usage.ConnectionDuration}");
            Console.WriteLine($"------------------");
        }
    }
}

Unfortunately, if you try this, you discover that the GetResults() call always fails with the exception E_ILLEGAL_METHOD_CALL. What’s going on?

There is a difference between TaskAwaiter.GetResult() and IAsyncAction.GetResult() (and its buddies like IAsyncOperation<T>.GetResult()).

The TaskAwaiter.GetResult() method waits for the task to complete before producing the results. On the other hand, IAsyncAction.GetResult() does not wait. It gives you the result if the asynchronous activity has completed, or it throws E_ILLEGAL_METHOD_CALL if the activity has not yet run to completion. You are expected to wait for the Completed callback before retrieving the result.¹

Now in this case, the situation is compounded by the fact that the program also marked the Main method as [STAThread]. Single-threaded apartments must pump messages while waiting; otherwise, you may run into deadlocks. So maybe it’s a good thing that this doesn’t work. Otherwise, you’d be tempted to use it in an incorrect way.

¹ You can find the code that enforces this in the method Check­Valid­State­For­Results­Call. We looked at this method some time ago.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.