Home > Back-end >  ASMX async System.InvalidOperationException: An asynchronous operation cannot be started at this tim
ASMX async System.InvalidOperationException: An asynchronous operation cannot be started at this tim

Time:07-22

While making some old web services (ASMX - ASp.Net) asynchronous, I ran into a problem with an error message in the browser after an ajax call to the service from javascript:

    Error (RetrieveDD): {
  "readyState": 4,
  "responseText": "System.InvalidOperationException: 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.\r\n   at System.Web.AspNetSynchronizationContext.OperationStarted()\r\n   at System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Create()\r\n   at SearchWebsite.DDService.LoadCountries(Int32 ID)\r\n",
  "status": 500,
  "statusText": "error"
}

The javascript side:

        if (servicename != "") {
        $.ajax({
            url: '../../DDService.asmx/'   servicename,
            dataType: 'json',
            method: 'post',
            data: { ID: id },
            success: function success(data) {
                ddl.empty();
                if (hid != "") {
                    $.each(data, function () {
                        if (this['V'] == hid) {
                            var newOption = new Option(this['T'], this['V'], true, true);
                            ddl.append(newOption);
                        } else {
                            var newOption = new Option(this['T'], this['V'], false, false);
                            ddl.append(newOption);
                        }
                    });
                } else {
                    $.each(data, function (index) {
                        if (index == 0) {
                            var newOption = new Option(this['T'], this['V'], true, true);
                            ddl.append(newOption);
                        } else {
                            var newOption = new Option(this['T'], this['V'], false, false);
                            ddl.append(newOption);
                        }
                    });
                };
                if (typeof callback === 'function') {
                    callback(data);
                };
            },
            error: function error(err) {
                console.log('Error (RetrieveDD): '   JSON.stringify(err, null, 2));
                if (typeof callback === 'function') {
                    callback(err);
                }
            }
        });
    };

And the method in the webservice (older asmx type):

    [WebMethod(Description = "Return list of Countries")]
    public async void LoadCountries(int ID)
    {          
        var retval = new List<DDListItemLight>();
        retval = await DropDownData.GetCountriesListAsync();
        string responseStr = JsonConvert.SerializeObject(retval);
        Context.Response.Write(responseStr);
    }

Anyway, before I made the webmethod async, everything works just fine. I've tried changing the signature to public async Task and public async Task as well with no luck. I thought ending the response would work, but it has no effect.

While looking around here and on the internet, the suggestion is to put Async="true" on the page header - but this is a webmethod not an ASPX page, so I was hoping there was some way to make this work (with old asmx). The MS documentation for this is a decade old and predates async/await (with no examples I could find)

Any ideas?

CodePudding user response:

ASMX does not support async. It was several generations old by the time async was introduced, so it was never updated to support async.

It does support APM-style asynchrony, so you can get this to work, but it will be awkward.

First, you need to ensure you're targeting .NET 4.5 or higher and have httpRuntime.targetFramework set to 4.5 or higher.

Then, you can define your API using APM methods and wrap the more natural TAP methods. The wrapping can be a bit painful, so I recommend using the ApmTaskFactory from my AsyncEx library, as such:

[WebMethod(Description = "Return list of Countries")]
public IAsyncResult BeginLoadCountries(int ID, AsyncCallback callback, object state)
{
  var task = LoadCountriesAsync(ID);
  return ApmAsyncFactory.ToBegin(task, callback, state);
}

private static async Task<string> LoadCountriesAsync(int ID)
{
  var retval = new List<DDListItemLight>();
  retval = await DropDownData.GetCountriesListAsync();
  return JsonConvert.SerializeObject(retval);
}

public void EndLoadCountries(IAsyncResult asyncResult)
{
  var responseStr = ApmAsyncFactory.ToEnd<string>(asyncResult);
  Context.Response.Write(responseStr);
}
  • Related