6
votes

I'm trying to use the async methods to upload a file to Azure blob storage and then set its metadata, but the UploadFromByteArrayAsync method never returns.

I have the following code:

var connAzureBlob = ConfigurationManager.AppSettings["AzureBlobStorage"];    
var storageAccount = CloudStorageAccount.Parse(connAzureBlob);
var blobClient = storageAccount.CreateCloudBlobClient();

var fileContainer = blobClient.GetContainerReference(ConfigurationManager.AppSettings["AzureBlobContainer"]);
if (!fileContainer.Exists())
{
    await fileContainer.CreateAsync();
    await fileContainer.SetPermissionsAsync(new BlobContainerPermissions {
                                                                             PublicAccess = BlobContainerPublicAccessType.Blob
                                                                         });
}

try
{
    var fileBlob = fileContainer.GetBlockBlobReference(documentId.ToString());
    await fileBlob.UploadFromByteArrayAsync(buffer, 0, buffer.Length);
    Log.Info($"{nameof(SaveToBlobAsync)}: blob {documentId} uploaded.");

    await fileBlob.FetchAttributesAsync();
    fileBlob.Properties.ContentType = contentType;
    fileBlob.Metadata["..."] = "...";
    await fileBlob.SetMetadataAsync();
    Log.Info($"{nameof(SaveToBlobAsync)}: {documentId} - metadata saved.");
}
catch (Exception exception)
{
    Log.Error($"{nameof(SaveToBlobAsync)}: An exception was thrown while saving a file to a blob: ", exception);
    throw;
}

Using the above code, I'd expect to see the following messages logged:

SaveToBlobAsync: blob 123 uploaded.
SaveToBlobAsync: 123 - metadata saved.

But these never appear.

However, the blob does appear to be stored (I can view its contents using the Azure Storage Explorer) but the process doesn't seem to move to the next line (which would be the first logging message).

If I switch all the async calls for their non-async counterparts then the code works as expected.

Can anyone explain why UploadFromByteArrayAsync doesn't seem to return?

Update: I thought I'd add some more information about the context, in case it helps.

This code is being called from a Web API method. The controller in question has no async code and does little else other than call a repository method. The repository method updates a SQL DB - again, all this is non-async code - before calling a class which interfaces with the blob storage.

This blob storage-facing class has non-async methods which do little else other than calling their async equivalents with either .Wait() or .Result.

In the case in question, the repository in the second stage is calling the non-async version, so the above code is essentially being called with .Wait().

Until the above code, there are no other async calls in this HTTP request from the point where it enters the Web API's controller.

1

1 Answers

9
votes

The error is almost certainly further up your call stack, where some method is blocking on the returned task (e.g., Task<T>.Result, Task.Wait, etc.). This will deadlock if the thread being blocked is required for the task to complete.

I explain this situation in full on my blog, but the gist of it is this:

  • await by default will capture a "context" and use that to resume the async method.
  • If this code is executed in an ASP.NET request context, this "context" is an ASP.NET SynchronizationContext; if this code is executed in a UI thread, this "context" is a UI SynchronizationContext.
  • Both the ASP.NET SynchronizationContext and UI SynchronizationContext only allow one thread in them at a time.

Thus, when the calling code blocks on the returned Task, it is holding a thread within that context, preventing the Task from completing.