Как избежать System.IO.FileLoadException при асинхронной записи в файл - PullRequest
0 голосов
/ 08 ноября 2019

Я пишу небольшое приложение UWP для чтения данных с датчика BLE (Bluetooth Low Energy). Единственная причина, по которой я использую UWP, заключается в том, что это единственная платформа, которая поддерживает BLE в Windows. Функциональность приложения очень проста, я просто подключаюсь к устройству, читаю данные с него и записываю его в csv. Часть с подключением Bluetooth уже решена и только косвенно связана с вопросом.

Способ, которым я пытался выполнить ведение журнала csv:

  • Кнопка в пользовательском интерфейсе подключается к void async Pick_FolderAsyncОбработчик событий
  • Этот метод выбирает папку через FolderPicker и создает внутри нее файл
  • StorageFolder и StorageFile сохраняются как свойства в моем классе MainPage и добавляются вFutureAccessList

Теперь к проблеме. Управление Bluetooth осуществляется в классе BluetoothManager, который подключается к устройству и определяет обработчик событий для изменения характеристики (терминология BLE для поступающих данных датчика):

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);
}

, который, в свою очередь, вызывает событие (OnUpdateCharacteristic), перехватывается методом класса MainPage:

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");
}

Именно в этой точке я получаю исключение System.IO.FileLoadException, поскольку файл не принадлежит вызывающему потоку.

Если бы я продолжил самостоятельно, я бы попытался получить Dispatcher для потока, в котором создается StorageFile, и затем вызвать его для выполнения операции записи, но, похоже, для этого требуется сборка WindowsBase.dll,что, скорее всего, излишне для этого.

Как вы, вероятно, можете сказать, я довольно новичок в C #, так что, возможно, есть несколько простых способов обойти это. Пожалуйста, не стесняйтесь спрашивать дополнительную информацию.

РЕДАКТИРОВАТЬ Трассировка стека исключения:

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

Ответы [ 2 ]

1 голос
/ 08 ноября 2019

Благодаря комментарию @Dortimer мне, конечно, нужно было синхронизировать свою работу. Однако проблема с большинством методов синхронизации .NET заключается в том, что они не допускают асинхронный код внутри синхронизированного блока (в данном случае мой вызов AppendTextAsync). Блокировка, которая работала для меня, была SemaphoreSlim, которую можно использовать для синхронизации асинхронного кода. (Для получения дополнительной информации см .: Асинхронное ожидание внутри C # Locks . Код выглядит следующим образом:

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();
        }
    }
0 голосов
/ 08 ноября 2019

У меня была похожая проблема некоторое время назад, когда несколько потоков хотели записать в один и тот же файл. Я решил, что это так:

  1. Я использовал System.Collections.Concurrentпространство имен, чтобы создать ConcurrentQueue<T>. Затем я могу свободно вызывать .Enqueue() из разных потоков.

  2. Я создал для этого какой-то «преобразователь», в моем случае я использовал System.Timers.Timer, который затем проходит каждые x секунд, чтоqueue и TryDequeue() будет принимать столько элементов, сколько было в очереди во время подсчета. Это гарантирует, что вы никогда не будете пытаться TryDequeue() больше раз, чем есть элементы, а любые новые, которые добавляются в queue, просто добавляются свободно и затем записываются в следующем цикле Timer.

Обратите внимание, что ваш queue, возможно, должен быть static для сохранения в нескольких потоках, в зависимости от вашего случая.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...