2

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?

  • When you do not have any choice; which is the best approach to use for “Sync over Async” and also “Async over Sync”?
  • 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.
  • 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.

Thanks in Advance. Please Suggest.

2 Answers2

3

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();
    }
}
Michael
  • 3,350
  • 2
  • 21
  • 35
  • "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." --- I did not get your above comment. Can you elaborate more please. – Dinesh Prajapati May 23 '20 at 11:40
  • Code change which you have suggested which leads to deadlock. Current UI Thread will block when async opreation comes and when async completed it wait for the Current UI thread to be available. which is not going to happen in either ways. So, We have to wrap in Task.Run to detach Current UI thread from Async operation. – Dinesh Prajapati May 25 '20 at 06:47
  • Then don't do async/await here: `AsyncHelper.RunSync(() => GraphService.GetMailFolders(_GraphServiceClient));` If you somewhere in the async chain uses async/await, you need to make sure that you don't return to the UI thread context by using `ConfigureAwait(false)`. I might be missing something in your code, so you might be right, but it just looked wrong. And you are still blocking the UI thread, no matter how many Tasks you are wrapping. – Michael May 25 '20 at 09:17
1

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.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • When you say "unavoidable drawbacks with any sync-over-async"; what could be those drawbacks comes in your mind considering my above situation? i understand to leverage best of async behavior we have to follow async principle by marking "async-await". However; sometimes; like i said; we DO NOT have choice; we can not rewrite hell lot of code. So considering above situation do you think above sync over async wrapper will be the next best solution than "async-await". – Dinesh Prajapati May 25 '20 at 08:14
  • Thanks for your input on "On Graph API to run on Thread pool"; i got your point on fetching only graph data on thread pool which ultimately frees the thread once graph data fetched.i will try that approach. However; does it involve overhead of context switching and will it be Major cost to Calling application or Calling Class's methods ? – Dinesh Prajapati May 25 '20 at 08:16
  • `what could be those drawbacks comes in your mind considering my above situation?` Well, one unavoidable drawback is that the code is synchronous. Other drawbacks depend on the exact type of hack used. For the thread pool hack (as in your proposed solution), the drawbacks are that the code has to work properly when called from a thread pool thread, and some minor performance hits: context switching and additional pressure on the thread pool. – Stephen Cleary May 25 '20 at 17:35
  • Thanks @Stephen Cleary. – Dinesh Prajapati Jun 01 '20 at 10:54