1

I am building an ASP.NET application. The application displays result from a third-party WebAPI. If the WebAPI takes more than 2 seconds, it will return the cached result.

The code below creates two async tasks, one for calling the WebAPI and one for retrieving from the cache. It also has a timer task. If the web task completes in less than 2 seconds, its result is returned. If the timer task ends first or the web task has an exception, the cached result is returned. The code runs so far so good.

public async Task<ActionResult> Index()
{
    Task<string> readFromCacheTask = ReadFromCacheAsync();
    Task<string> readFromWebTask = ReadFromWebAsync();

    try
    {
        Task timerTask = Task.Delay(2000);
        Task completedTask = await Task.WhenAny(readFromWebTask, timerTask);
        await completedTask;
        if (readFromWebTask.Status == TaskStatus.RanToCompletion)
        {
            return Content(readFromWebTask.Result);
        }
    }
    catch (Exception)
    {
        // Return cache result if there is an error getting from web.
    }
    return Content(await readfromCacheTask);
}

Question: What I want in addition is allowing the web task to continue running even after the cached result is returned to the caller so that an updated web result will be cached and will be available for the next caller.

What I have in mind is spawning an async background thread using Task.Run in Application_Start. The thread contains a task collection and runs in a continuous loop. When the controller is called, it creates a readFromWebTask and adds it to the task collection in the background thread.

Is this a good approach? Are there better ways to achieve?

Clarification @2019-02-27 14:46: The readFromWebTask contains the logic for updating the cache. From the run-time observation, ASP.NET does not let readFromWebTask to run past the request completion. The task was aborted and did not run to the end. In following example, only Foo was written in the text file. Bar is not there.

public class HomeController : Controller
{
    public async Task DelayAndLog()
    {
        string path = @"C:\temp\test.txt";
        System.IO.File.AppendAllText(path, string.Format("{0}: Foo", DateTime.Now));
        await Task.Delay(2000);
        System.IO.File.AppendAllText(path, string.Format("{0}: Bar", DateTime.Now));
    }


    public async Task<ActionResult> Index()
    {
        Task a = DelayAndLog();
        await Task.Delay(1);
        return Content("Hello World");
    }
}
Tony
  • 1,827
  • 1
  • 22
  • 23
  • 1
    check out https://www.hangfire.io/ seems to do what you want – jamesSampica Feb 27 '19 at 21:25
  • How often should the cache be refreshed? – Enigmativity Feb 27 '19 at 21:28
  • 2
    Very unclear what you are asking for - there is no code shown in the post that will cancel the `readFromWebTask` task... so eventually it will finish in some way... which sound exactly behavior you want. So could you please clarify what you still missing of current behavior? (obviously letting tasks run past request completion is dangerous, but it does not look like you have problems with that) – Alexei Levenkov Feb 27 '19 at 21:45
  • 1
    ASP.NET Classic already has a task collection that even ties into lifetime services: `HostingEnvironment.QueueBackgroundWorkItem`. But as Alexei pointed out, the code *already does* what you're asking for it to do; you don't need the task collection, whether built-in or custom. – Stephen Cleary Feb 27 '19 at 21:54
  • Just as an experiment: what if you do `Task a = DelayAndLog().ConfigureAwait(false);`? Does it still write only Foo to the file? – Yeldar Kurmangaliyev Feb 27 '19 at 23:04
  • @Alexei and @Stephen: That is the assumption I had initially. However in runtime, ASP.NET does not let `readFromWebTask` to complete to the end. Please see the clarification I have added. – Tony Feb 28 '19 at 00:13
  • @Shoe: Thanks for the suggestion. HangFire seems to be a nice library, but it is heavy duty. I can use `Task.When` to be notified when the web task is complete or it is time out. However, HangFire does not have an easy for me to do the same thing. Moreover, I want to avoid creating a new thread for each request. – Tony Feb 28 '19 at 00:44
  • You are looking for https://stackoverflow.com/questions/40639255/asp-net-mvc-5-long-running-task-how-to-ensure-that-worker-thread-wont-be-th and links from it. And check https://stackoverflow.com/questions/29938522/difference-between-task-run-and-queuebackgroundworkitem-in-asp-net – Alexei Levenkov Feb 28 '19 at 01:58
  • @Yeldar: It has the same behavior after I changed the code to `DelayAndLog().ConfigureAwait(false);`. However, Bar is written to the file if I change the code to `Task.Delay(2000).ConfigureAwait(false);` I found this [article](https://blogs.msdn.microsoft.com/benwilli/2017/02/09/an-alternative-to-configureawaitfalse-everywhere/) is helpful for understanding ConfigureAwait. – Tony Mar 01 '19 at 00:56

0 Answers0