How to avoid System.IO.FileLoadException when writing to file asynchronously

0

I am writing a small UWP app to read data from a BLE (Bluetooth Low Energy) sensor. The only reason I am using UWP is because it is the only plattform that supports BLE in Windows. The functionality of the app is very simple, I just connect to the device, read data from it and log it to a csv. The Bluetooth connectivity part is already solved and only circunstantial to the question.

The way I tried to accomplish the csv logging:

  • A button in the UI connects to the void async Pick_FolderAsync Event Handler
  • This method picks a folder through a FolderPicker and creates a file inside it
  • The StorageFolder and StorageFile are stored as properties in my MainPage class and are added to the FutureAccessList

Now to the problem. The Bluetooth management is done in a BluetoothManager class, which connects to the device and defines an Event Handler for the characteristic change (BLE terminology for sensor data coming through):

public event EventHandler<UInt64> UpdateCharacteristic;
public delegate void UpdateCharacteristicEventHandler(object sender, UInt64 e);
protected virtual void OnUpdateCharacteristic(UInt64 e) => UpdateCharacteristic?.Invoke(this, e);

private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
    var reader = DataReader.FromBuffer(args.CharacteristicValue);
    UInt64 res = reader.ReadUInt64();
    OnUpdateLog("Characteristic " + sender.Uuid + " changed. New value: " + res);
}

Which in turn triggers an Event (OnUpdateCharacteristic), to be caught by a method of the MainPage class:

BluetoothManager.UpdateCharacteristic += Update_CharacteristicAsync;

private async void Update_CharacteristicAsync(object sender, ulong e) => await Update_CharAsync(e);

private async Task Update_CharAsync(ulong e)
{
    await FileIO.AppendTextAsync(LogFile, DateTime.Now.ToLongDateString() + ";" + e + "\n");
}

It is in this point where I get a System.IO.FileLoadException, as the file does not belong to the calling Thread.

If I were to continue on my own, I would try to get a Dispatcher for the Thread where the StorageFile is created and then invoke it to perform the write operation, but this seems to require the WindowsBase.dll assembly, which is very likely overkill for this.

As you can probably tell, I am pretty new in C#, so there is probably some easy way around this. Please do not hesitate to ask for more information.

EDIT The Stack Trace of the exception:

System.IO.FileLoadException
  HResult=0x80070020
  Message=Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird.
  Source=System.Private.CoreLib
  StackTrace:
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Device_Reader.MainPage.<Update_CharAsync>d__26.MoveNext() in D:\...\MainPage.xaml.cs:line 107
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Device_Reader.MainPage.<Update_CharacteristicAsync>d__25.MoveNext() in D:\...\MainPage.xaml.cs:line 103
c#
.net
uwp
asked on Stack Overflow Nov 8, 2019 by drilow • edited Nov 8, 2019 by drilow

2 Answers

1

Thanks to the comment by @Dortimer I of course needed to synchronize my operation. The problem with most of .NET's synchronization methods, however, is that they don't allow asynchronous code inside the synchronized block (in this case my call to AppendTextAsync). The lock that worked for me was SemaphoreSlim, which can be used to synchronize asynchronous code. (For more info see: Async Waiting inside C# Locks. The code looks like this:

static SemaphoreSlim LogFileSemaphore = new SemaphoreSlim(1, 1);

private async void Update_CharacteristicAsync(object sender, ulong e)
    {
        await LogFileSemaphore.WaitAsync();
        try
        {
            await FileIO.AppendTextAsync(LogFile, DateTime.Now.ToLongDateString() + ";" + e + "\n");
        } finally
        {
            LogFileSemaphore.Release();
        }
    }
answered on Stack Overflow Nov 8, 2019 by drilow
0

I had a similar issue some time ago, with multiple threads wanting to write to the same file, the way i solved it was like this:

  1. I used the System.Collections.Concurrent namespace, to create a ConcurrentQueue<T>. I can then freely call .Enqueue() from different threads.

  2. I created some 'resolver' for this, in my case I used a System.Timers.Timer that then elapses every x seconds, that would take the queue and TryDequeue() for as many items as were in the queue at count time. This ensures that you never attempt to TryDequeue() more times than there are items, and any new ones that are added to the queue are just freely added and then written on the next cycle of the Timer.

Note that your queue might need to be static to persist across multiple threads, depending on your case.

answered on Stack Overflow Nov 8, 2019 by Trickermand

User contributions licensed under CC BY-SA 3.0