1

I have a controller action, which gets a list of document types, then makes a webservice call for each document type. I want to make these all at once, so that looping over them only takes as long as the longest one. I don't know if my code is correct, and I need to do something else, or if my code is simply incorrect.

Action:

public ActionResult GetPlan(MemberViewModel request)
{
    DocService ds = new DocService();

    List<DocType> docTypes = ds.GetDocTypesForPlan(request.PlanId);

    List<CoverageDocument> coverageDocuments = ds.GetDocumentsForDocTypes(docTypes);

    return View(coverageDocuments);
}

GetDocumentsForDocTypes:

public List<CoverageDocument> GetDocumentsForDocTypes(List<DocType> planDocTypes)
{
    List<CoverageDocument> planDocuments = new List<CoverageDocument>();

    DocumentUtility documentUtility = new DocumentUtility();
    int lastYear = DateTime.Now.Year - 1;

    planDocTypes.ForEach(async (docType) =>
    {
        DocumentUtility.SearchCriteria sc = new DocumentUtility.SearchCriteria();
        sc.documentType = docType;
        Dictionary<long, Tuple<string, string>> documentList = await documentUtility.FindDocuments(sc);

        documentList.ToList().ForEach((document) =>
            {
                CoverageDocument doc = this.coverageDocumentConstructor(document);
                planDocuments.Add(doc);
            });
    });

    return planDocuments;
}

Exception:

Additional information: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>. This exception may also indicate an attempt to call an "async void" method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.

1 Answer 1

6

Your code is incorrect. By sending an async lambda to the ForEach extension method you're forcing it to be async void which is never a good idea outside UI event handlers.

To actually be asynchronous your calls need to be async all the way:

public async Task<ActionResult> GetPlan(MemberViewModel request)
{
    DocService ds = new DocService();

    List<DocType> docTypes = ds.GetDocTypesForPlan(request.PlanId);

    List<CoverageDocument> coverageDocuments = await ds.GetDocumentsForDocTypesAsync(docTypes);

    return View(coverageDocuments);
}

public async Task<List<CoverageDocument>> GetDocumentsForDocTypesAsync(List<DocType> planDocTypes)
{
    DocumentUtility documentUtility = new DocumentUtility();
    int lastYear = DateTime.Now.Year - 1;

    var planDocuments = await Task.WhenAll(planDocTypes.Select(async (docType) =>
    {
        DocumentUtility.SearchCriteria sc = new DocumentUtility.SearchCriteria();
        sc.documentType = docType;

        return await documentUtility.FindDocuments(sc).Select((document) => this.coverageDocumentConstructor(document))
    }));

    return planDocuments.SelectMany(doc => doc);;
}
Sign up to request clarification or add additional context in comments.

6 Comments

Ok, there's the async void I was reading about. It seems I shouldn't have to be async all the way up the stack. So I now have to change my interface signature(s)? Is a Parallel.ForEach what I want (different?)?
I3arnon, For clarity you need to change your code from documentUtility.FindDocuments(sc) to documentUtility.FindDocumentsAsync(sc) assuming that the FindDocument supports Async.
@Son_of_Sam That's not my code, it's the OP's. But that's true, as I did in the other methods
@MStodd That depends on what you're trying to achieve. If all you want to use multiple threads to parallelize CPU intensive work, then yes. If FindDocuments is truly async though, that would be a waste of threads.
@I3arnon not CPU intensive, relatively long (5 sec) web service calls. FindDocuments is async (I own it), and it makes an async service call. I could change it to make a sync call instead, and use Parallel.Foreach
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.