Reputation: 99
I am new to this Asynchronous world So bear with my knowledge.
Currently; we are working on implementing Graph API for getting exchange data like Mail Folders, messages etc. Since Graph API follows APM (Asynchronous Programming Model) we ran in to situation “Sync over Asyc”.
_GraphServiceClient.Me.MailFolders.Request.GetAsync()
Background
Let me put the situation; Mail Sending and different mails related code lies in our Root Class Library which has Synchronous methods.
Those methods will be called from different classes of the same library (i.e. executing workflow step will send mail etc) and from ASP.NET Web application, Windows Forms application, Console Application. Now, we can not mark mail sending class’s method to Async as Async virus will spread and we can not make all caller to be Async. There will be hell lot of code to refactor.
From Past few days; I have been reading below related insight full articles of (Stephen Toub & Stephen Cleary”) and few SO posts.
So, after reading relevant articles above and others over web; I think below approach I need to use. Introduce the Asynchelper wrapper which executes the Async method in another thread of thread pool (using Task.Run) and block the current thread.
GraphService Class: which makes all graph communication and get relevant results.
static class GraphService
{
public async static Task<List<MailFolder>> GetMailFolders(GraphServiceClient graphClient)
{
UserMailFoldersCollectionPage mFolders = await graphClient.Me.MailFolders.Request.GetAsync();
return mFolders.CurrentPage;
}
}
AsyncHelper
using System;
using System.Threading.Tasks;
internal static class AsyncHelper
{
// Private ReadOnly _myTaskFactory As TaskFactory = New TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.[Default])
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
// Dim cultureUi = CultureInfo.CurrentUICulture
// Dim culture = CultureInfo.CurrentCulture
// Return _myTaskFactory.StartNew(Function()
// Thread.CurrentThread.CurrentCulture = culture
// Thread.CurrentThread.CurrentUICulture = cultureUi
// Return func()
// End Function).Unwrap().GetAwaiter().GetResult()
return Task.Run(() =>
{
return func();
}).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static void RunSync(Func<Task> func)
{
// Dim cultureUi = CultureInfo.CurrentUICulture
// Dim culture = CultureInfo.CurrentCulture
// _myTaskFactory.StartNew(Function()
// Thread.CurrentThread.CurrentCulture = culture
// Thread.CurrentThread.CurrentUICulture = cultureUi
// Return func()
// End Function).Unwrap().GetAwaiter().GetResult()
Task.Run(() =>
{
return func();
}).ConfigureAwait(false).GetAwaiter().GetResult();
}
}
Usage in Root Class library:
List<MailFolder> mFolders = AsyncHelper.RunSync(async () => await GraphService.GetMailFolders(_GraphServiceClient));
Could you please help me in this architecture design issue?
Thanks in Advance. Please Suggest.
Upvotes: 3
Views: 5409
Reputation: 456322
Let me put the situation; Mail Sending and different mails related code lies in our Root Class Library which has Synchronous methods. Now, we can not mark mail sending class’s method to Async as Async virus will spread and we can not make all caller to be Async. There will be hell lot of code to refactor.
When you do not have any choice; which is the best approach to use for “Sync over Async” and also “Async over Sync”?
To be very clear: you can avoid sync-over-async; you just don't want to. And there's nothing wrong with that decision; refactoring to async
can be nontrivial and should be balanced with other concerns like actually shipping value to customers. But recognize that it is a decision and that there are unavoidable drawbacks with any sync-over-async solution.
When you do not have any choice; which is the best approach to use for “Sync over Async” and also “Async over Sync”?
There is no "best choice", or else everyone would just use that and there would be no difficulty or discussion around the matter. Each choice has different pros and cons, and there is no solution that works everywhere.
If the Graph API can be called from thread pool threads, then the thread pool hack that you have should be fine. Just a minor note: the ConfigureAwait(false)
in your method does nothing, since there is no await
to configure.
I think “Async” & “Await” operator does not cause additional thread to be created; then how asynchrony takes place in the same thread. Technically; how "Async" & "Await" achieve asynchronous behaviour with out creating a new thread? Actually; I am bit confused between Task.Run & Async await.
It is certainly confusing at first. The core idea of async
/await
is to use fewer threads. This is possible for many operations (such as I/O), because the thread can be used for something else while the operation is actually happening.
Upvotes: 2
Reputation: 3631
Comments on some of you questions:
When you do not have any choice; which is the best approach to use for “Sync over Async” and also “Async over Sync”?
If you want to go from something that is async to sync, you need to wait, and thus blocking a thread. I think your AsyncHelper is okay, but I see no reason why you create and additional Task to wait on. Just wait on the Task given from the Func. And ConfigureAwait(false)
only have effect when used with await
. See below.
I think “Async” & “Await” operator does not cause additional thread to be created; then how asynchrony takes place in the same thread. Technically; how "Async" & "Await" achieve asynchronous behaviour with out creating a new thread? Actually; I am bit confused between Task.Run & Async await.
I believe the documentation says that it is not garantied that new threads are created. It might create new threads. It also depends on the Context
.
In GraphService Class as described above; it returns only current page data. We need to fetch all mail folders so that i guess we need to perform loop and again execute the same requrest to fetch all mail folders So i think; we might need to follow Asynchelper.RunSync option in that class if we need to follow that approach.
Be aware that async/await doesn't mean multitasking. You could take a look at TPL (Task Parallel Library).
AsyncHelper:
internal static class AsyncHelper
{
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
return func().GetAwaiter().GetResult();
}
public static void RunSync(Func<Task> func)
{
func().Wait();
}
}
Upvotes: 3