Using async/await operators in C#, part 2

Using async/await operators in C#, part 2

This article reviews usage of the async/await operators, which became available in .NET Framework 4.5. They are part of the Task-based Asynchronous Pattern (TAP), which elegantly solves the problem of efficient thread synchronization. The main purpose of introducing async/await operators is to prevent the thread from being blocked by waiting for results of other threads. Such thread consumes system resources but doesn’t perform any useful action. It would be better to release the thread during waiting time and create a new one (or reuse a free one) as soon as possible to continue work.

This is the second part of the series.
Please follow this link to read the first part: Using async/await operators in C#, part 1.

Changing default behavior of the await operator

As you may remember from previous examples, the await operator splits a function into two parts: synchronous and asynchronous. But it’s possible to change that behavior and force code which is located after await operator to run in any other thread. SynchronizationContext Class can be used for that purpose. Let’s consider some members of that class:
Method 1.

public static void SetSynchronizationContext(SynchronizationContext syncContext);

Method 2.

public static SynchronizationContext Current { get; }

First member allows overriding the SyncronizationContext of current thread. Note that you have to call SetSynchronizationContext in all threads where you want to override the default behavior.  Second member allows getting the SyncronizationContext of current thread. Default value of the SynhronizationContext.Current for newly created threads is null. But sometimes for existing threads (such as a main thread of WPF application) it may be different from null. Therefore you need to check value of SynhronizationContext.Current in order to understand how the await operator works in each particular case.

Instance of the SynchronizationContext class provides default synchronization behavior, so it cannot be used for custom behavior implementation. Thus, it’s possible to inherit from SynchronizationContext and override its members. The following method should be overridden in order to change behavior of the await operator:

public virtual void Post(SendOrPostCallback d, object state);

This method invokes asynchronous part of asynchronous function. Behavior of the await operator is defined by the thread where a callback function ’d’ is invoked by function Post. Let’s consider the following example:

using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
class Program
{
static void Main(string[] args)
{
//Thread #1
Task task = LogAsync("Program started");

//Wait until logged
task.Wait();
}

private async static Task LogAsync(string @event)
{
//Thread #1
await Task.Run(() =>
{
//Thread #2
Thread.Sleep(1000); // Simulate saving into database
});

//Thread #2
Thread.Sleep(1000); // Simulate saving into file system
}
}
}

We assume that the second Thread.Sleep of function LogAsync should be performed in the main thread without changing LogAsync function. Let’s use the Dispatcher design pattern in order to do that. The following interface will be declared:

public interface IDispatcher
{
void Invoke(ActionData action);
void WaitForActions();
ActionData GetAction();
}

The ActionData class here has the following prototype:

public class ActionData
{
public SendOrPostCallback Action { get; set; }
public object State { get; set; }
}

The main responsibility of that dispatcher is to invoke code in the main thread. This action is performed by the Invoke method. Method WaitForAction will lock current thread in order to wait for action to be invoked. Previously, it was told that it’s a bad practice to lock threads for waiting. But the main function cannot be interrupted for waiting time because application will stop working. Therefore it is possible to use WaitForAction method in the main function. Third method allows getting information about current action after WaitForAction unblocks the thread.

Now custom SynchronizationContext may be defined. Its implementation will be quite simple:

public class CustomSynchronizationContext : SynchronizationContext
{
private IDispatcher dispatcher;

public CustomSynchronizationContext(IDispatcher dispatcher)
{
this.dispatcher = dispatcher;
}

public override void Post(SendOrPostCallback d, object state)
{
dispatcher.Invoke(new ActionData()
{
Action = d,
State = state
});
}
}

Now let’s prepare implementation of IDispatcher:

public class Dispatcher : IDispatcher
{
private List<ActionData> actions = new List<ActionData>();
private ManualResetEvent waitEvent = new ManualResetEvent(false);

public void Invoke(ActionData action)
{
lock (actions)
{
actions.Add(action);
waitEvent.Set();
}
}

public ActionData GetAction()
{
if (!waitEvent.WaitOne(0))
throw new InvalidOperationException("No action has come yet. Please use WaitForActions in order to wait for incoming actions");

ActionData result;

lock (actions)
{
result = actions.First();
actions.RemoveAt(0);
if (!actions.Any())
{
waitEvent.Reset();
}
}

return result;
}

public void WaitForActions()
{
waitEvent.WaitOne();
}
}

Incoming actions will be added to queue named ‘actions’. ManualResetEvent named waitEvent is used for performing waiting and thread synchronization. Let’s complete that example with some code for main function:

public class Program
{
private static IDispatcher dispatcher = new Dispatcher();

static void Main(string[] args)
{
SynchronizationContext.SetSynchronizationContext(new CustomSynchronizationContext(dispatcher));

//Thread #1
LogAsync("Program started");

//Thread #1

while (true)
{
dispatcher.WaitForActions();

ActionData actionData = dispatcher.GetAction();
actionData.Action(actionData.State);
}
}

private static async void LogAsync(string @event)
{
//Thread #1
await Task.Run(() =>
{
//Thread #2
Thread.Sleep(1000); // Simulate of saving into database
});

//Thread #1
Thread.Sleep(1000); // Simulate of saving into file
Console.WriteLine("Logging is completed successfully");
}
}

After applying the await operator code located after it will run in thread #1! It might look like code executes synchronously but in reality there is a quite complex logic behind that process. Loop in the main function will be unlocked just after thread #2 ends. Saving event to file will be invoked in the context of main thread. This complicated process is not visible for programmers who are developing LogAsync function. It means that async/await provides easy syntax for difficult things.

One of the drawbacks of this technique that you may notice is that the main function will never stop because of the infinite loop. Windows applications have similar loop inside but with an ability to exit from this loop when the application is closing. Example above becomes more complex if the same is done for console application. This simple implementation was used there in order to demonstrate possibilities of the SynchronizationContext class.

Exception handling

Await operator supports exception handling as well. It’s possible to catch exception which was thrown by synchronous or asynchronous part of asynchronous function. Try…catch block will look as usual:

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
Task<string> task = GetContentOrNull("invalid address");
task.Wait();
}

private static async Task<string> GetContentOrNull(string url)
{
try
{
using(HttpClient client = new HttpClient())
using (HttpResponseMessage message = await client.GetAsync(url))
{
return await message.Content.ReadAsStringAsync();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
return null;
}
}
}
}

Please note that exception is thrown on await operator. Therefore, even if your method just returns a task from other asynchronous function, as LogEventWrapper function does below:

using System;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
try
{
Task task = LogEventWrapper(“Program started”);
task.Wait();
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}

private static Task LogEventWrapper(string @event)
{
return LogEvent(@event);
}

private static Task LogEvent(string @event)
{
return Task.Run(() =>
{
throw new InvalidOperationException("Cannot save into database");
});
}
}
}

it’s better to apply await operator:

private static async Task LogEventWrapper(string @event)
{
await LogEvent(@event);
}

Function LogEventWrapper will perform the same actions but it’s easier to add try…catch block there:

using System;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
Task task = LogEventWrapper("Program started");
task.Wait();
}

private static async Task LogEventWrapper(string @event)
{
try
{
await LogEvent(@event);
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}

private static Task LogEvent(string @event)
{
return Task.Run(() =>
{
throw new InvalidOperationException("Cannot save into database");
});
}
}
}

If exception is thrown in synchronous part of asynchronous function then catch block will be performed synchronously in the caller thread. Example with thread distribution is presented below:

using System;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
//Thread #1
Task<string> task = GetContentOrNull("invalid address");
task.Wait();
}

private static async Task<string> GetContentOrNull(string url)
{
try
{
//Thread #1
return await GetContent();
}
catch (Exception exception)
{
//Thread #1
Console.WriteLine(exception.ToString());
return null;
}
}

private static async Task<string> GetContent()
{
//Thread #1
throw new NotSupportedException();
}
}
}

If exception is thrown in asynchronous part of asynchronous function, then catch block will be performed asynchronously in the calling thread. Thread distribution is presented below:

using System;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
//Thread #1
Task<string> task = GetContentOrNull(“invalid address”);
task.Wait();
}

private static async Task<string> GetContentOrNull(string url)
{
try
{
//Thread #1
return await GetContent();
}
catch (Exception exception)
{
//Thread #2
Console.WriteLine(exception.ToString());
return null;
}
}

private static async Task<string> GetContent()
{
return await Task.Run(new Func<string>(()=>
{
//Thread #2
throw new InvalidOperationException();
}));
}
}
}

So, try…catch block catches exception in the same thread where asynchronous function ends. It may be even main thread if exception is thrown in asynchronous part of asynchronous function and SynchronizationContext is set, as in case of WPF application:

XAML:

<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
<Button Name="button" Click="Button_Click_1">Click me</Button>
</Window>

Code:

using System;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void Button_Click_1(object sender, RoutedEventArgs e)
{
try
{
//Thread #1
await Get();
//Thread #1
button.Content = "Completed";
}
catch (Exception exception)
{
//Thread #1
button.Content = exception.ToString();
}
}

private async Task Get()
{
await Task.Run(() =>
{
//Thread #2
throw new InvalidOperationException();
});
}
}
}

Exception from asynchronous operation may be caught on function Wait of class Task as well. But in that case AggregateException will be thrown.

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
try
{
Task<string> task = GetContentOrNull("invalid address");
task.Wait();
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}

private static async Task<string> GetContentOrNull(string url)
{
using (HttpClient client = new HttpClient())
using (HttpResponseMessage message = await client.GetAsync(url))
{
return await message.Content.ReadAsStringAsync();
}
}
}
}

One more advantage of async/await operators is that original exception is thrown by await operator unlike Task.Wait where AggregateException is thrown. So, operators async/await allow catching exceptions quite easily and it doesn’t matter in which part of asynchronous function it has been thrown. But it’s important to understand which thread will be used for performing a catch.

Converting old-style asynchronous methods to async/await style

When you start using the async/await operators in your application you may notice that they should be applied to almost all the code. Otherwise you might face difficulties during new features implementation. Another advantage of using async/await is that they are supported very well by .NET Framework 4.5. For example, if you want to use sockets in order to connect to remote server, you may use the following task-based method:

public Task ConnectAsync(IPAddress address, int port);

Therefore the first rule for await/async usage is the following: just check if class that you want to use asynchronously supports awaitables (returns Task or Task<T>).

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
Task&amp;amp;amp;amp;lt;bool&amp;amp;amp;amp;gt; task = Connect("127.0.0.1", 80);

WriteProgress(task);

Console.WriteLine(task.GetAwaiter().GetResult() ? "Connection succeeded" : "Connection failed");
}

private static void WriteProgress(Task&amp;amp;amp;amp;lt;bool&amp;amp;amp;amp;gt; task)
{
while (task.Status == TaskStatus.Running || task.Status == TaskStatus.WaitingForActivation)
{
Thread.Sleep(100);
Console.Write(".");
}
}

private static async Task&amp;amp;amp;amp;lt;bool&amp;amp;amp;amp;gt; Connect(string address, ushort port)
{
using (TcpClient tcpClient = new TcpClient())
{
try
{
await tcpClient.ConnectAsync(address, port);
}
catch (SocketException)
{
return false;
}

return true;
}
}
}
}

Sometimes no task-based functions are present. .NET Framework may help you with resolving several such standard situations. For example, if asynchronous model in your application is based on the IAsyncResult/AsyncCallback pattern, then code may be converted easily into task-based style. For that purpose Task.Factory.FromAsync function may be used:

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace TestProgram
{
public class Program
{
static void Main(string[] args)
{
Task&amp;amp;amp;amp;lt;bool&amp;amp;amp;amp;gt; task = Connect("127.0.0.1", 80);

WriteProgress(task);

Console.WriteLine(task.GetAwaiter().GetResult() ? "Connection succeeded" : "Connection failed");
}

private static void WriteProgress(Task&amp;amp;amp;amp;lt;bool&amp;amp;amp;amp;gt; task)
{
while (task.Status == TaskStatus.Running || task.Status == TaskStatus.WaitingForActivation)
{
Thread.Sleep(100);
Console.Write(".");
}
}

private static async Task&amp;amp;amp;amp;lt;bool&amp;amp;amp;amp;gt; Connect(string address, ushort port)
{
using (TcpClient tcpClient = new TcpClient())
{
try
{
await Task.Factory.FromAsync(new Func&amp;amp;amp;amp;lt;string, int, AsyncCallback, object, IAsyncResult&amp;amp;amp;amp;gt;(tcpClient.BeginConnect),
new Action&amp;amp;amp;amp;lt;IAsyncResult&amp;amp;amp;amp;gt;(tcpClient.EndConnect),
address,
port,
TaskCreationOptions.None);
}
catch (SocketException)
{
return false;
}

return true;
}
}
}
}

As you may see, methods BeginConnect end EndConnect of TcpClient were converted into Task by the Task.Factory.FromAsync method.

Web services support task-based function as well. In order to generate it, just allow generation of the Task-based asynchronous methods in Configure Service References dialog.

Everything that you need to do in order to update your old code is configure service references and use newly created asynchronous functions in your code. Don’t be afraid to use async/await operators in existing applications because old-style asynchronous code may be easily converted into awaitable functions in the majority of practical cases.

Representing synchronous function as asynchronous

Sometimes architecture of application requires use of an asynchronous function, but its implementation must be synchronous. As example let’s consider Burd’s Proxy Searcher and its interface for detecting proxy location by IP address:

public interface IGeoIP
{
Task&amp;amp;amp;amp;lt;CountryInfo&amp;amp;amp;amp;gt; GetLocation(string ipAddress);
}

TurnOffGeoIP class implements IGeoIP interface and was created for disabling GeoIP in application.

public class TurnOffGeoIP : IGeoIP
{
public async Task&amp;amp;amp;amp;lt;CountryInfo&amp;amp;amp;amp;gt; GetLocation(string ipAddress)
{
return await Task.Run&amp;amp;amp;amp;lt;CountryInfo&amp;amp;amp;amp;gt;(() =&amp;amp;amp;amp;gt; new CountryInfo
{
Code = null,
Name = string.Empty
});
}
}

The drawback of this code is that new thread is created but no asynchronous operation is required. If Task.FromResult method is used, then code becomes better:

using System.Threading.Tasks;

namespace ProxySearch.Engine.GeoIP
{
public class TurnOffGeoIP : IGeoIP
{
public async Task&amp;amp;amp;amp;lt;CountryInfo&amp;amp;amp;amp;gt; GetLocation(string ipAddress)
{
return await Task.FromResult(new CountryInfo
{
Code = null,
Name = string.Empty
});
}
}
}

Now new thread is not being created anymore, but the function looks like asynchronous and matches the IGeoIP interface.

References:

1. MSDN: Task-based Asynchronous Pattern (TAP)

Summary
Using async/await operators in C#, part 2 – Abto Software, New York
Article Name
Using async/await operators in C#, part 2 – Abto Software, New York
Description
The second part of the blog is about the async/await operators available in .NET Framework 4.5+ and their usage for efficient thread synchronization.
Publisher Name
Abto Software
Publisher Logo

Contact us

Tell your idea, request a quote or ask us a question