DEV Community

Prasanna Sridharan
Prasanna Sridharan

Posted on

Understanding Task in .Net

In .Net framework, there are couple of ways to implement asynchronous programming. We will look at the Task Parallel Library, which also happens to be the recommended way since .Net 4.0.

Using Task, with async-await keyword, the methods return a promise to the function execution.

Lets say there is some long running operation to execute. If we want to offload the long running operation to "someone" else so that the main thread can focus on doing some other work, it will be really beneficial if

  • The long running operation is purely I/O based operation; which means that waiting on the operation to complete, without offloading it to another thread, would waste CPU cycles.
  • Its a heavy CPU based application, and its a multicore machine, where the OS can optimize the CPU.

For purpose of this example, just created a P/Invoke to query all the open windows (just to simulate an example of managed-native boundary with call back to managed code).

// Import user32.dll (containing the function we need) and define
// the method corresponding to the native function.
[DllImport("user32.dll")]
private static extern int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

private static Dictionary<Int64, int> keyValuePairs = new Dictionary<Int64, int>();

// Define the implementation of the delegate; here, we simply output the window handle.
private static bool OutputWindow(IntPtr hwnd, IntPtr lParam)
{
     var x = hwnd.ToInt64();
     if (!keyValuePairs.ContainsKey(x)) {
         keyValuePairs.Add(x, 0);
     }
     keyValuePairs[x]++;
     return true;
}

Enter fullscreen mode Exit fullscreen mode

Lets take a look at the invocation as below:

private static async Task PInvokeUsingTPL()
{
    // Invoke the PInvoke call
    var handle = Task.Run(() => EnumWindows(OutputWindow, IntPtr.Zero));
    Console.WriteLine("Executed code to the PInvoke");


    // Now until the handle returns main thread of application is free to do something else
    while(handle.Status != TaskStatus.RanToCompletion)
    {
        Console.WriteLine("Doing something else");
        Thread.Sleep(500);
    }
    Console.WriteLine("handle returns and we can continue");
    await handle;
    Console.WriteLine($"Found {keyValuePairs.Count} unique windows");

}
Enter fullscreen mode Exit fullscreen mode

First statement within the method actually invokes the method that makes the native external call. Given that it enumerates through the windows that are open on the machine, the main thread gets handle / promise to the execution.
While the main thread awaits, it can check the status of the handle and perform some other operation.

Note the execution:
Shows the order of execution of code, where the main thread is free to do some other work while awaiting the response from native code invocation via TPL

Of course, though i used the TPL's Task.Run method to offload the execution to another .Net framework to execute as thread pool, one can still use the old construct of using threadpool manually as below:

private static async Task PInvokeUsingThreadPool()
{
    Console.WriteLine("Starting invoke");
    var response = PureThreadPoolInvocation();
    Console.WriteLine("Invoked with threadpool and now i can do more stuff");
    await response;
    Console.WriteLine("Thread pool invocation complete");
}

private static Task PureThreadPoolInvocation()
{
    TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
    ThreadPool.QueueUserWorkItem(_ =>
    {
        EnumWindows(OutputWindow, IntPtr.Zero);
        taskCompletionSource.SetResult(true);
    });

    return taskCompletionSource.Task;
}
Enter fullscreen mode Exit fullscreen mode

Disclaimer: there are few thread.sleep methods used just to explain the sequence of execution; one would not use them in the production grade code unless its absolutely necessary.

Oldest comments (0)