3

I have the following async long running method inside my asp.net mvc-5 web application :-

 public async Task<ScanResult> ScanAsync(string FQDN)
 {
    // sample of the operation i am doing 
    var c = await context.SingleOrDefaultAsync(a=>a.id == 1);
    var list = await context.Employees.ToListAsync();
    await context.SaveChangesAsync();
    //etc..
}

and i am using Hangfire tool which support running background jobs to call this async method on timely basis, but un-fortuntly the hangefire tool does not support calling async methods directly . so to overcome this problem i created a sync version of the above method , as follow:-

public void Scan()
{
    ScanAsync("test").Wait();
}

then from the HangFire scheduler i am calling the sync method as follow:-

RecurringJob.AddOrUpdate(() => ss.Scan(), Cron.Minutely);

so i know that using .Wait() will mainly occupy the iis thread during the method execution ,, but as i mentioned i need to do it in this way as i can not directly call an async TASK inside the hangefire scheduler .

so what will happen when i use .Wait() to call the async method ?, will the whole method's operations be done in a sync way ? for example as shown above i have three async operations inside the ScanAsync() ;SingleOrDefualtAsync,ToListAsync & SaveChangesAsync, so will they be executed in sync way because i am calling the ScanAsync method using .wait() ?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
John John
  • 1
  • 72
  • 238
  • 501
  • Can't you call it like this? `RecurringJob.AddOrUpdate(async () => await ss.ScanAsync(), Cron.Minutely);` – Igor Sep 18 '15 at 15:21
  • One thing I believe you'll need to do is check the task to see if it had an exception. Something like this: `var task = ScanAsync("test"); task.Wait(); if(task.Exception != null) {throw task.Texception};` If you don't do this, you may not find out if you had an exception in your background job. – mason Sep 18 '15 at 15:23
  • @Igor No, Hangfire specifically does not support directly executing an async method. See [this issue](https://github.com/HangfireIO/Hangfire/issues/150). – mason Sep 18 '15 at 15:23
  • @Igor it will raise an error , and in this case the Startupclass where the "RecurringJob.AddOrUpdate(async () => await ss.ScanAsync(), Cron.Minutely);" is defined will need to be an async tasks which will not work, because i can not use await inside non-async task method – John John Sep 18 '15 at 15:25

2 Answers2

3

so what will happen when i use .Wait() to call the async method ?, will the whole method's operations be done in a sync way ? for example as shown above i have three async operations inside the ScanAsync() ;SingleOrDefualtAsync,ToListAsync & SaveChangesAsync, so will they be executed in sync way because i am calling the ScanAsync method using .wait() ?

The methods querying the database will still be executed asynchronously, but the fact that you're calling Wait means that even though you're releasing the thread, it wont return to the ASP.NET ThreadPool as you're halting it.

This is also a potential for deadlocks, as ASP.NET has a custom synchronization context which makes sure the context of the request is availiable when in a continuation of an async call.

I would recommend that instead, you'd use the synchronous API provided by entity-framework, as you won't actually be enjoying the scalability one can get from asynchronous calls.

Edit:

In the comments, you asked:

As i am currently doing with hangefire eliminate the async effect ? if yes then will it be better to use sync methods instead ? or using sync or async with hangeffire will be exactly the same

First, you have to understand what the benefits of async are. You don't do it because it's cool, you do it because it serves a purpose. What is that purpose? Being able to scale under load. How does it do that? When you await an async method, control is yielded back to the caller. For example, you have an incoming request, you query you database. You can either sit there and wait for the query to finish, or you can re-use that thread to serve more incomong requests. That's the true power.

If you don't actually plan on receiving a decent amount of requests (such that you'll starve the thread-pool), you won't actually see any benefit from async. Currently, the way you've implemented it, you won't see any of those benefits because you're blocking the async calls. All you'll see, possibly, are deadlocks.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • What I'm not clear on (and I think John is not sure of) is how much that matters in a Hangfire environment, where Hangfire itself will handle executing the passed lambda. – mason Sep 18 '15 at 15:20
  • @Yuval can you adivce more on what do u mean by "I would recommend that instead, you'd use the synchronous API provided by entity-framework, as you won't actually be enjoying the scalability one can get from asynchronous calls." ?? – John John Sep 18 '15 at 15:22
  • @mason I'm not too sure about the `HangFire` environment as I haven't used it myself. But I don't think this has to do with `HangFire` at all, it just schedules the delegates. – Yuval Itzchakov Sep 18 '15 at 15:23
  • @YuvalItzchakov but if i understand the situation well, now the .Wait() will cause a whole iis thread to be occupied ... but the other async operations will still be executed in async way. so why do u recommend to chnage all the method to be sync ? I mean in my situation having some methods executed in async way is better than having non-async at all .. is this correct ? – John John Sep 18 '15 at 15:30
  • 1
    @mason Hangfire still runs under IIS/ASP.NET and consumes threads. If Hangfire was able to use asynchronous methods, it could release them to the pool while waiting for the database. As it is, using synchronous methods means the compiler creates slightly simpler code because it doesn't have to generate the code needed to implement `async/await` – Panagiotis Kanavos Sep 18 '15 at 15:30
  • @PanagiotisKanavos Hangfire can use asynchronous methods. It just can't accept an asynchronous delegate. So therefore it can run async and still release the threads, right? – mason Sep 18 '15 at 15:35
  • @PanagiotisKanavos so what do you suggest in mycase; 1) to keep the code as is . 2) to remove the .Wait() as mentioned on the below reply ? 3) to chnage my async method to be sync ? – John John Sep 18 '15 at 15:36
  • 1
    @johnG #3 if the code is used only by Hangfire. There's no benefit in using async code until Hangfire itself introduces async support. #1 If `ScanAsync` is also called by controller methods (probably not a good idea). #2 will simply break Hangfire and its dashboard. You won't be able to see running jobs or cancel them if you have to, as the job will complete immediately and leave an orphaned Task running – Panagiotis Kanavos Sep 18 '15 at 15:47
  • @PanagiotisKanavos Your last comment was a good summary. Might I suggest you edit that into your answer? – mason Sep 18 '15 at 15:54
  • @PanagiotisKanavos why there is not any benefit of using async methods with hang-fire ? as i understand still the action method's operations such as ToListAsync(), SaveChnagesAsync are done on async way , while on thread will stay occupied due to .Wait()? is this correct ? – John John Sep 18 '15 at 16:11
  • @mason i am a bit confused , now is using async methods as i am currently doing with hangefire eliminate the async effect ? if yes then will it be better to use sync methods instead ? or using sync or async with hangeffire will be exactly the same ? – John John Sep 18 '15 at 16:14
  • 1
    @johnG They'll never be exactly the same. At the very least, async code is more complex because the compiler has to create a state machine, so it's a little more overhead. Not a big amount, so unless your're really pressed for resources you probably won't have to worry about that. – mason Sep 18 '15 at 16:17
0

This very much depends on the way HangFire is implemented. If it just queuing tasks to be invoked in ThreadPool the only effect will be, that one of your threads will be blocked until the request is ended. However if there is a custom SynchronizationContext this can lead to a serious deadlock.

Consider, if you really want to wait for your scheduled job to be done. Maybe all you want is just a fire and forget pattern. This way your method will be like:

public void Scan()
{
    ScanAsync("test"); // smoothly ignore the task
}

If you do need to wait, you can instead try using async void method:

public async void Scan()
{
    await ScanAsync("test");
    DoSomeOtherJob();
}

There are many controversies about using async void as you cannot wait for this method to end, nor you can handle possible errors.

However, in event driven application this can be the only way. For more informations you can refer to: Async Void, ASP.Net, and Count of Outstanding Operations

Community
  • 1
  • 1
Kędrzu
  • 2,385
  • 13
  • 22
  • Web applications aren't event driven. In fact, Stephen Cleary says *don't use async void` in that question. Anyway, running long running jobs in ASP.NET requires a lot of plumbing, you can't just use `async void` because it may be terminated as soon as the main request finishes processing – Panagiotis Kanavos Sep 18 '15 at 15:35
  • @Kedrzu so do u mean if i simply remove .Wait() then i will avoid or minimize the possibility of deadlocks ? – John John Sep 18 '15 at 15:38
  • What you described as "smoothly ignore the task" means "I don't care if the task fails or not". Which is most likely not the case. Your first code would introduce a condition that would silently fail. And there is no "DoSomeOtherJob" as this `Scan` method is purely a sync wrapper around `ScanAsync`. – mason Sep 18 '15 at 15:40
  • This is the question of using a job scheduler in ASP app at first. Job scheduler is event driven. Maybe the real solution is to throw it away from ASP :) – Kędrzu Sep 18 '15 at 15:44
  • @mason Yes, this is entirerly true. I think the real question is, whether that is a problem or not. Maybe failing to do some recurring job from time to time is not a big deal. This will depend on requirements. – Kędrzu Sep 18 '15 at 15:48
  • @Kędrzu Whether the failure is "not a big deal or not" isn't the issue. The issue is that it would silently fail. Perhaps it wouldn't be a big deal if the job occasionally failed, but it *would* be a big deal if the job failed *every time* and you didn't know because you didn't have the code check the task. – mason Sep 18 '15 at 15:50
  • @Kędrzu schedulers aren't necessarily event driven, in fact web farms use *queues*, not events, as it's uncertain which server will execute each job. Hangfire *does* use queues, typically stored in SQL Server. Running background jobs in ASP.NET is a complex job, as described [here by Scott Hanselman](http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx). – Panagiotis Kanavos Sep 18 '15 at 15:50