4

I used the following code to download/save an image and open it later, but in later OpenAsync, it throws the UnauthorizedAccessException, it seems that the file is not close, but actually the IRandomAccessStream/DataWriter has been disposed.

HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://www.silverlightshow.net/Storage/Users/nikolayraychev/Perspective_Transforms_4.gif");
HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

//Write Image File
StorageFile imageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("test.gif", CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream fs = await imageFile.OpenAsync(FileAccessMode.ReadWrite))
{
    using (DataWriter writer = new DataWriter(fs.GetOutputStreamAt(0)))
    {
        writer.WriteBytes(await response.Content.ReadAsByteArrayAsync());
        await writer.StoreAsync();
        writer.DetachStream();
        await fs.FlushAsync();
    }
 }

 StorageFile imageFile1 = await ApplicationData.Current.LocalFolder.GetFileAsync("test.gif");
 //Exception is throwed here
 using (IRandomAccessStream stream = await imageFile1.OpenAsync(FileAccessMode.Read))
 {
     BitmapImage img = new BitmapImage();
     img.SetSource(stream);
}
Rob
  • 45,296
  • 24
  • 122
  • 150
Yanzhi
  • 73
  • 2
  • 7
  • You don't need to flush any writer or stream. They flush on Dispose. Why would they just throw away your data? – usr Jun 23 '12 at 09:51
  • await fs.FlushAsync() is just ensure the async flush operation completed. – Yanzhi Jun 23 '12 at 10:24

3 Answers3

6

I had the same issue and had to explicitly dispose the stream and file objects before it would complete.

    var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, Windows.Storage.CreationCollisionOption.ReplaceExisting);
    using (var fs = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
    {
        var outStream = fs.GetOutputStreamAt(0);
        var dataWriter = new Windows.Storage.Streams.DataWriter(outStream);
        dataWriter.WriteString("Hello from Test!");
        await dataWriter.StoreAsync();
        dataWriter.DetachStream();
        await outStream.FlushAsync();
        outStream.Dispose(); // 
        fs.Dispose();
    }
0

You cannot "using" when you use 'await'. The reason is how the compiler transform your C# await/async to IL. You can decompile to see it.

When the processor arrives to:

await writer.StoreAsync();

in reality it returns to the caller immediately (see IL). Since you are using "using", the Dispose interface of `IRandomAccessStream fs is called and the resources are released. These resources are required by the thread initiated in the 'StoreAsync'.

For this reason, you must call Dispose explicitly, after the await.

The same problem appears in try/exception/catch blocks.

Dabiel Kabuto
  • 2,762
  • 4
  • 29
  • 45
  • I don't think it's true that you can't use using in an async method. – Anthony Wieser Dec 28 '13 at 10:45
  • Sorry if my english was not clear. Of course you can use "using" in an async method, but you need to be careful when you use the result returned by "await" within a "using". await/sync is just a language sugar. Basically it saves a context and returns a Task instance to the caller. It does NOT wait for the function in the way some users expect. You can see it in the IL code. It is important to know it when you use "using" because then "strange" behaviors happen, as described below. – Dabiel Kabuto Jan 14 '14 at 20:58
  • Sorry, but that's simply wrong: http://stackoverflow.com/questions/16566547/do-using-statements-and-await-keywords-play-nicely-in-c-sharp – Stefan Jun 29 '16 at 07:17
0

It looks to me that you're leaking the stream passed to

    using (DataWriter writer = new DataWriter(fs.GetOutputStreamAt(0)))

If the stream is reference counted (it is winRT after all), then a reference will be held by the temporary object passed to the constructor, and incremented by the constructor in DataWriter.

The temporary object will be awaiting garbage collection.

Does it work if you instead do this:

using (var st0 = fs.GetOutputStreamAt(0))
    using (DataWriter writer = new DataWriter(st0))
Anthony Wieser
  • 4,351
  • 1
  • 23
  • 25