3

Something is definitely flawed in my understanding of async/await. I want a piece of code named SaveSearchCase to run asynchronously in background.

I want it to be fired and forget about it and continue with the current method's return statement.

public IList<Entities.Case.CreateCaseOutput> createCase(ARC.Donor.Data.Entities.Case.CreateCaseInput CreateCaseInput, ARC.Donor.Data.Entities.Case.SaveCaseSearchInput SaveCaseSearchInput)
{
    ..........
    ..........
    ..........
    var AcctLst = rep.ExecuteStoredProcedure<Entities.Case.CreateCaseOutput>(strSPQuery, listParam).ToList();

    if (!string.IsNullOrEmpty(AcctLst.ElementAt(0).o_case_seq.ToString()))
    {
        Task<IList<Entities.Case.SaveCaseSearchOutput>> task = saveCaseSearch(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);
        Task t = task.ContinueWith(
                r => { Console.WriteLine(r.Result); }
         );
    }
    Console.WriteLine("After the async call");
    return AcctLst;
}

And the SaveCaseSearch looks like

public async Task<IList<Entities.Case.SaveCaseSearchOutput>> saveCaseSearch(ARC.Donor.Data.Entities.Case.SaveCaseSearchInput SaveCaseSearchInput,Int64? case_key)
{
    Repository rep = new Repository();
    string strSPQuery = string.Empty;
    List<object> listParam = new List<object>();
    SQL.CaseSQL.getSaveCaseSearchParameters(SaveCaseSearchInput, case_key,out strSPQuery, out listParam);
    var AcctLst = await rep.ExecuteStoredProcedureAsync<Entities.Case.SaveCaseSearchOutput>(strSPQuery, listParam);
    return (System.Collections.Generic.IList<ARC.Donor.Data.Entities.Case.SaveCaseSearchOutput>)AcctLst;
}

But when I see the debugger createCase method waits for SaveCaseSearch to complete first and then only

it prints "After Async Call "

and then returns . Which I do not want definitely .

So which way is my understanding flawed ? Please help to make it run async and continue with current method's print and return statement .

UPDATE

I updated the SaveCaseSearch method to reflect like :

public async Task<IList<Entities.Case.SaveCaseSearchOutput>> saveCaseSearch(ARC.Donor.Data.Entities.Case.SaveCaseSearchInput SaveCaseSearchInput,Int64? case_key)
{
    return Task.Run<IList<Entities.Case.SaveCaseSearchOutput>>(async (SaveCaseSearchInput, case_key) =>
    {
        Repository rep = new Repository();
        string strSPQuery = string.Empty;
        List<object> listParam = new List<object>();
        SQL.CaseSQL.getSaveCaseSearchParameters(SaveCaseSearchInput, case_key, out strSPQuery, out listParam);
        var AcctLst = await rep.ExecuteStoredProcedureAsync<Entities.Case.SaveCaseSearchOutput>(strSPQuery, listParam);
        return (System.Collections.Generic.IList<ARC.Donor.Data.Entities.Case.SaveCaseSearchOutput>)AcctLst;
    });
}

But there is something wrong with the params. It says

Error   4   A local variable named 'SaveCaseSearchInput' cannot be declared in this scope because it would give a different meaning to 'SaveCaseSearchInput', which is already used in a 'parent or current' scope to denote something else C:\Users\m1034699\Desktop\Stuart_V2_12042016\Stuart Web Service\ARC.Donor.Data\Case\Search.cs   43  79  ARC.Donor.Data
StrugglingCoder
  • 4,781
  • 16
  • 69
  • 103
  • When you use the async functionality you should pass callback into your function and after it do all things you need you call that callback and thats how you use async, because when you use return statment it will run sync – layonez Apr 13 '16 at 08:20
  • if (!string.IsNullOrEmpty(AcctLst.ElementAt(0).o_case_seq.ToString())) { SaveCaseSearch ...} I want to call this method at the background async . Could you please tell me how to achieve the same ? – StrugglingCoder Apr 13 '16 at 08:23
  • you should use await keyword when calling `saveCaseSearch` method and then print the result. – hendryanw Apr 13 '16 at 08:25
  • I do not want to do anything with the result of SaveCaseSearch . But just calling SaveCaseSearch() gave me compiler warning . Can you please help me call async ? – StrugglingCoder Apr 13 '16 at 08:26
  • 1
    Possible duplicate of [Fire and Forget approach](http://stackoverflow.com/questions/22864367/fire-and-forget-approach) – Liam Apr 13 '16 at 08:27
  • 1
    The compiler probably tells you that if you don't use await keyword, it will run synchronously. And also the method will still runs on the main thread (not really fire and forget), but it is not blocking the thread when you execute the stored procedure. – hendryanw Apr 13 '16 at 08:27
  • T;DR async/await isn't really for fire and forget. Use a Task. – Liam Apr 13 '16 at 08:28
  • Possibly the same issue as I hit before - http://stackoverflow.com/questions/33913227/web-service-running-unexpectedly-synchronously ? – eftpotrm Apr 13 '16 at 10:35

2 Answers2

2

Well this saveCaseSearch() method runs synchronously in main thread and this is the main problem here. Instead of returning result with a task you should return Task with operation itself. Here is some simplified example :

Runs synchronously and waits 5 seconds

    public IList<int> A()
    {

        var AcctLst = new List<int> { 0, 2, 5, 8 };

        if (true)
        {
            Task<IList<int>> task = saveCaseSearch();

            Task t = task.ContinueWith(
                    r => { Console.WriteLine(r.Result[0]); }
             );
        }

        Console.WriteLine("After the async call");

        return AcctLst;
    }

    // runs sync and in the end returns Task that is never actually fired
    public async Task<IList<int>> saveCaseSearch()
    {
        Thread.Sleep(5000);
        return new List<int>() { 10, 12, 16 };
    }

Runs asynchronously - fires task & forgets :

    public IList<int> A()
    {
        ... same code as above
    }

    // notice that we removed `async` keyword here because we just return task.
    public Task<IList<int>> saveCaseSearch()
    {
        return Task.Run<IList<int>>(() =>
        {
            Thread.Sleep(5000);
            return new List<int>() { 10, 12, 16 };
        });
    }

Here is full code for this example

Fabjan
  • 13,506
  • 4
  • 25
  • 52
  • Actually inside the code of SaveCaseSearch we have something like await rep.ExecuteStoredProcedureAsync(strSPQuery, listParam); is it fine then to wrap everything up by Task.Run<>() ? i.e. can await comes within Task.Run<>()? – StrugglingCoder Apr 13 '16 at 10:09
  • @StrugglingCoder Yes, you can add `async` keyword to method inside the task, for this example it would be : `Task.Run>(*async* () => { });` – Fabjan Apr 13 '16 at 10:11
  • @StrugglingCoder This looks like different question, ask it please or update your question with new code and problem – Fabjan Apr 13 '16 at 10:47
  • @StrugglingCoder You don't really need to pass parameters inside your lambda you can use something called [*closure*](http://www.codethinked.com/c-closures-explained) instead. Remove parameters from `async (SaveCaseSearchInput, case_key) => {} ` leaving it just `async () => {} ` and it should work. – Fabjan Apr 13 '16 at 10:59
0

Against all that I believe in pertaining to "fire-and-forget" you can do this by writing your code this way:

public Task<SaveCaseSearchOutput> SaveCaseSearch(
    SaveCaseSearchInput saveCaseSearchInput,
    long? caseKey)
{
    var rep = new Repository();
    var query = string.Empty;
    var listParam = new List<object>();
    SQL.CaseSQL
       .getSaveCaseSearchParameters(
           saveCaseSearchInput, 
           caseKey, 
           out query,
           out listParam);

    return rep.ExecuteStoredProcedureAsync<SaveCaseSearchOutput>(
        strSPQuery, 
        istParam);
}

And then if the place where you would like to fire it and log when it returns (which is really what you have -- so you're not forgetting about it), do this:

public IList<CreateCaseOutput> CreateCase(
    CreateCaseInput createCaseInput,
    SaveCaseSearchInput saveCaseSearchInput)
{
    // Omitted for brevity...

    var AcctLst = 
        rep.ExecuteStoredProcedure<CreateCaseOutput>(
            strSPQuery,
            listParam)
           .ToList();

    if (!string.IsNullOrEmpty(AcctLst.ElementAt(0).o_case_seq.ToString()))
    {
        SaveCaseSearch(saveCaseSearchInput,
                       AcctLst.ElementAt(0).o_case_seq)
            .ContinueWith(r => Console.WriteLine(r.Result));
    }

    Console.WriteLine("After the async call");
    return AcctLst;
}

The issue was that you were using async and await in the SaveSearchCase function, and this basically means that your code is the opposite of "fire-and-forget".


As a side note, you should really just use async and await, and avoid the "fire-and-forget" idea! Make your DB calls asynchronous and leverage this paradigm for what it's worth!

Consider the following:

The SaveCaseSearch call can stay as I have defined it above.

public Task<SaveCaseSearchOutput> SaveCaseSearch(
    SaveCaseSearchInput saveCaseSearchInput,
    long? caseKey)
{
    var rep = new Repository();
    var query = string.Empty;
    var listParam = new List<object>();
    SQL.CaseSQL
       .getSaveCaseSearchParameters(
           saveCaseSearchInput, 
           caseKey, 
           out query,
           out listParam);

    return rep.ExecuteStoredProcedureAsync<SaveCaseSearchOutput>(
        strSPQuery, 
        istParam);
}

Then in your call to it, do this instead:

public async Task<IList<CreateCaseOutput>> CreateCase(
    CreateCaseInput createCaseInput,
    SaveCaseSearchInput saveCaseSearchInput)
{
    // Omitted for brevity...

    var AcctLst = 
        await rep.ExecuteStoredProcedureAsync<CreateCaseOutput>(
            strSPQuery,
            listParam)
           .ToList();

    if (!string.IsNullOrEmpty(AcctLst.ElementAt(0).o_case_seq.ToString()))
    {
        await SaveCaseSearch(saveCaseSearchInput,
                       AcctLst.ElementAt(0).o_case_seq)
            .ContinueWith(r => Console.WriteLine(r.Result));
    }

    Console.WriteLine("After the async call");
    return AcctLst;
}

This makes for a much better solution!

David Pine
  • 23,787
  • 10
  • 79
  • 107