Rowland Shaw
Rowland Shaw

Reputation: 38130

How to handle concurrency with StorageFile operations?

I'm attempting to write to a file but "occasionally" I run into issues that I think are down to concurrency, as some of the time, I'm getting a System.UnauthorizedAccessException with message:

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

...from the following:

public async void SubmitChanges()
{
    DataContractSerializer serializer = 
        new DataContractSerializer(typeof(LocalCache<T>));

    StorageFile file = await ApplicationData.Current.LocalFolder
            .CreateFileAsync(GetFileNameForType(), 
                CreationCollisionOption.ReplaceExisting);

    //Occasionally throws here
    using (var fi = await file.OpenTransactedWriteAsync())
    {
        serializer.WriteObject(fi.Stream.AsStreamForWrite(), this);
        await fi.CommitAsync();
    }
}

I can only assume that this is down to some concurrency, or it being still open for read somewhere else, but I can't seem to find a way to wait for it to become available - I'd normally lock around it, but this is not allowed with await so what are the other options?

Upvotes: 1

Views: 2209

Answers (2)

Jared Bienz - MSFT
Jared Bienz - MSFT

Reputation: 3550

There are a number of things that could be happening here. As Stephen mentioned above, your encapsulating method does not return a task. This means that whatever method is calling SubmitChanges is calling it with a "fire and forget" pattern and code that follows this call can be run in parallel. This is probably not what you want.

In addition, I notice you're using StorageFile.OpenTransacted. I've not used this before but the notes indicate OpenTransacted is only supported on operating systems that support ReplaceFile. This feature literally allows the new file to assume the identity of the old file, but I'm pretty sure that operation would fail if the old file is open.

I don't think the attempt to swap file identities would happen until the transacted file is closed, which would happen on Dispose, which happens automatically due to the using statement, which is the line you're sometimes seeing the exception.

I would recommend you return a Task instead and I would also consider using the regular StorageFile.OpenAsync.

Upvotes: 1

Stephen Cleary
Stephen Cleary

Reputation: 456507

Usually, you just don't start the next operation until the previous operation is complete.

Tip: avoid async void; use async Task instead. That enables you to know when the previous operation is complete.

Upvotes: 6

Related Questions