Simon
Simon

Reputation: 9425

When using FileStream.WriteAsync() what happens when the method is called again

I am recording a video stream to disk. For this I use the following code:

private async Task SaveToFile(IntPtr data, Int32 size)
{
    if (_FileStream == null) return;
    Byte[] buf = new Byte[size];
    Marshal.Copy(data, buf, 0, size);
    //dont await here - just continue
    _FileStream.WriteAsync(buf, 0, size);
}

So far it seems to work without issue. What I just want to confirm is what happens if this method is called before the previous iteration has finished.

You will notice I don't await on the WriteAsync() call. I'm not sure if this is correct, but the API I am using states to keep operations to a minimum inside this method, so as to not block the callback internally in the API. This seems like to correct thing to do, simply pass the data to the stream, and return immediately.

Can someone please confirm what happens if the last WriteAsync() call hasn't finished, and I call WriteAsync() again? Or should I be awaiting the WriteAsync() call?

EDIT: I am yet to experience exceptions using the above method, so I guess my last question is are there any repercussions when using WriteAsync() from inside an external DLL callback? The DLL is a third party Directshow component. I cannot verify how it internally works, but it simply provides me with data via a callback, in which i save to the filestream.

Upvotes: 6

Views: 7376

Answers (3)

NeddySpaghetti
NeddySpaghetti

Reputation: 13495

FileStream is not thread safe. Two writes writing to the same file from different threads will not work correctly.

As pointed out by @Noseratio making overlapping calls to WriteAsync from the same thread is fine.

So if you are writing to the file from different threads, you need to synchronize access to the file.

On a side note, I would aslo modify your SaveToFile to return the task, and because you are not using await the method doesn't need to be async either.

private Task SaveToFile(IntPtr data, Int32 size)
{
    if (_FileStream == null) return;
    Byte[] buf = new Byte[size];
    Marshal.Copy(data, buf, 0, size);
    //dont await here - just continue
    return _FileStream.WriteAsync(buf, 0, size);
}

Upvotes: 1

noseratio
noseratio

Reputation: 61666

There is nothing wrong with asynchronous overlapping WriteAsync operations, as long as they write to different, non-overlapping segments of the file:

using System;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var tempFile = System.IO.Path.GetTempFileName();
            Console.WriteLine(tempFile);

            var fs = new System.IO.FileStream(
                tempFile, 
                System.IO.FileMode.Create, 
                System.IO.FileAccess.ReadWrite, 
                System.IO.FileShare.ReadWrite, 
                bufferSize: 256,
                useAsync: true);

            fs.SetLength(8192);

            var buff1 = Enumerable.Repeat((byte)0, 2048).ToArray();
            var buff2 = Enumerable.Repeat((byte)0xFF, 2048).ToArray();

            try
            {
                fs.Seek(0, System.IO.SeekOrigin.Begin);
                var task1 = fs.WriteAsync(buff1, 0, buff1.Length);

                fs.Seek(buff1.Length, System.IO.SeekOrigin.Begin);
                var task2 = fs.WriteAsync(buff2, 0, buff2.Length);

                Task.WhenAll(task1, task2).Wait();
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

On the low level, it all comes down to WriteFile API with non-null LPOVERLAPPED lpOverlapped, which is a supported scenario in Windows API.

If the writes do overlap, you still won't see an error, but the writes will race and the end result may be unpredictable.

Upvotes: 6

Peter Duniho
Peter Duniho

Reputation: 70652

You will notice I don't await on the WriteAsync() call. I'm not sure if this is correct, but the API I am using states to keep operations to a minimum inside this method, so as to not block the callback internally in the API.

You haven't shown enough code for any of us to know exactly what you're doing here. However, it's clear you probably need some help understanding exactly how async methods work.

In particular, using the await statement does not block the method's execution. In fact, quite the opposite: it causes the method to return at that point.

The other thing that it does is set up a continuation for the awaited task, so that when that task does complete, the continuation is executed. In your method, the call to the WriteAsync() method is the last thing the method does, so there's not really any continuation. But using await would have a another important effect: it allows the Task returned by your method to correctly indicate completion of the entire method, including the call to WriteAsync().

As things stand now, your method returns immediately after calling WriteAsync() (i.e. before the write operation actually completes), and it appears to the caller to have completed, even though the write operation hasn't necessarily completed yet.

In your example, your method writes to an apparent class member _FileStream. Calling the method again before it's completed the first time would definitely be bad; I doubt you'd get exceptions, but FileStream doesn't in any way guarantee coherent writes to the file if you execute them concurrently.

Probably the best thing here is to just not use async and tolerate blocking on the write. If blocking on the write really does turn out to be a problem for the API you are using, then you should use a queue of buffers and a separate sequence of asynchronous operations to write to the file instead of calling WriteAsync() every time the API calls you to write data.

Upvotes: 3

Related Questions