Await Async Returns on New Thread


#1

I am writing a Windows Service to extract data from our SQL server and export it to Realm. Some of this data includes new users who need to be created on the ROS so they can login and download their data.

My code currently enumerates each of the users to check if they already exist and if not creates them as follows;

// Process the driver data...
var drivers = GetDriverData(table);
foreach (DataRow row in drivers.Rows)
{
	var driver = secureDriverService.GetById(row["driverId"].ToString());
	if (driver == null)
	{
		var userPin = random.Next(10001, 99999);
		var secureDriver = new SecureDriver
		{
			Id = row["driverId"].ToString(),
			UserName = row["userName"].ToString(),
			FirstName = row["firstname"].ToString(),
			LastName = row["lastname"].ToString(),
			PIN = userPin.ToString()
		};
		secureDriver = secureDriverService.Add(secureDriver);
		Trace.WriteLine($"Thread ID (Start): 
 System.Threading.Thread.CurrentThread.ManagedThreadId}");
		var user = await RealmServices.AddSyncServerUser("https://<myRealmInstance>.us1.cloud.realm.io", secureDriver.UserName, secureDriver.PIN);
		Trace.WriteLine($"Thread ID (End)  : {System.Threading.Thread.CurrentThread.ManagedThreadId}");
	}
}

And RealmServices,AddSyncServerUser is;

		public static async Task<User> AddSyncServerUser(string serverUrl, string userName, string password)
		{
			var credentials = Credentials.UsernamePassword(userName, password, createUser: true);

			var authUrl = new Uri(serverUrl);
			var user = await User.LoginAsync(credentials, authUrl);
			return user;
		}

My problem is that “await RealmServices.AddSyncServerUser” will often return on a new thread and consequently the next call to “secureDriverService.GetById;” raises the error “Realm accessed from incorrect thread”. This is confirmed by the trace statements on either side of the call to my async method.

Everything I have read regarding await/async states they do not create new threads so I am at a loss as to what is happening.


#2

await/async doesn’t create threads on its own, but also doesn’t guarantee that the task continuation will be scheduled on the same thread. You can read about it here, but the gist of it is that on threads without synchronization context, the continuation is posted on the threadpool (i.e. random thread). Typically the main thread on UI-based applications has synchronization context, but console apps and windows services, do not. One way to work around it is to use a 3rd party package, e.g. Nito.AsyncEx to install a context on your thread. The usage is very simple:

public void Main()
{
    AsyncContext.Run(() => MainAsync());
}

private async Task MainAsync()
{
    // your async code
}

#3

Thanks Nikola - will refactor my code to us Nito.AsyncEx :slight_smile: