Я сейчас создаю приложение MVVM, одна из моих моделей представлений использует Сервис, зарегистрированный с внедрением зависимостей. Эта служба запускает команды Powerhell командлета или http REST для различных сторонних приложений, которые не очень рады, когда получают несколько запросов одновременно.
Вот почему я хотел бы иметь возможность запускать несколько операций из пользовательского интерфейса (не блокируя его), но следя за тем, чтобы служба обрабатывала только по одной за раз. Мои элементы пользовательского интерфейса покажут, если они работают или ожидают.
Я пытался реализовать TPL ActionBlock, но до сих пор все мои операции выполнялись одновременно, и единственный способ заставить их работать в очереди блокирует пользовательский интерфейс до тех пор, пока все задачи не будут выполнены.
Вот что я сделал:
Моя модель представления содержит коллекцию ObservableCollection элементов, которая содержит два списка (один вложен в другой). В пользовательском интерфейсе он выглядит как список элементов, который можно развернуть, чтобы показать небольшое древовидное представление.
Я хочу, чтобы каждый раз, когда я раскрываю элемент, все вложенные элементы в древовидном представлении проверяли свой статус в стороннем приложении через службу.
Метод в подпункте пользовательского интерфейса выглядит следующим образом:
private async Task<bool> UpdateSubItemsStatus()
{
foreach (var item in connectorsMenuItems)
{
await parent.Library.EnqueueConnectorOperations(Connectors.service.OperationType.CheckPresence, parent.CI, AssetId, item.ConnectorID, parent.ConnectorInterfaces.Single(c => c.ItemId == AssetId).ItemsConnectorPresence.Single(i => i.ConnectorId == item.ConnectorID));
}
return true;
}
Здесь «parent» - это элемент 1-го уровня, а «parent.Library» - модель основного вида, в которой размещается все.
На модели View методы, которые получают это, следующие:
public async Task EnqueueConnectorOperations(OperationType operationType, ConfigurationItem ci, Guid itemId, Guid ConnectorID, ItemConnectorPresence itemPresence)
{
logManager.WriteLog($"Library : Received connector operation for item {itemId}, the name of the item is {itemPresence.ItemName}", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), LogManagement.LogLevel.Information);
//Set requestor UI item in working state in the UI
if(ci.CIType == EnumCIType.Application)
{
LibraryItems.Single(l => l.CI.ID == ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == ConnectorID).IsWorking = true;
LibraryItems.Single(l => l.CI.ID == ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == ConnectorID).Status = LibraryItemState.UpdatingStatus;
LibraryItems.Single(l => l.CI.ID == ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == ConnectorID).StatusString = "Checking Presence";
}
ActionBlock<OperationType> actionBlock = new ActionBlock<OperationType>(async _operationType =>
{
logManager.WriteLog($"Library : Sending the operation to connector service : item {itemId}, the name of the item is {itemPresence.ItemName}", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), LogManagement.LogLevel.Information);
await connectorService.EnqueueConnectorOperations(operationType, ci, itemId, Settings.Default.ProjectLocalPath + @"\" + ci.ID.ToString(), ConnectorID, Settings.Default.DisplayLanguage, Settings.Default.ProjectLocalPath, itemPresence).ConfigureAwait(false);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1,
CancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(5)).Token,
});
actionBlock.Post(operationType);
actionBlock.Complete();
actionBlock.Completion.Wait();
}
Тогда служба, названная здесь «connectorService», выполняет свою работу.
Здесь, в последней строке, если я использую actionBlock.Completion.Wait (), все задачи выполняются последовательно, мой пользовательский интерфейс заблокирован.
Если я использую вместо этого, ожидаем actionBlock.Completion (). Пользовательский интерфейс не заблокирован, но все работает параллельно.
Так что, если бы у кого-то был совет, было бы здорово!
ОБНОВЛЕНИЕ:
Я адаптировал ответчик JSteward для своих нужд:
Я объявил ActionBlock, как вы рекомендовали, в качестве частного члена моей модели представления. Но когда я сделал, как вы сказали, когда я израсходовал элемент, он функционировал там, где очередь стояла правильно, но если я развернул другой элемент, то его операции (которые также были в их очереди) выполнялись параллельно операциям первого элемента. Что не является поведением, я ожидаю только одну операцию за раз, независимо от того, сколько элементов запрашивают.
Итак, я сделал следующие изменения:
ActionBlock инициализируется в конструкторе модели представления раз и навсегда:
public ViewModelCtor()
{
actionBlock = new ActionBlock<ConnectorOperationArgWrapper>(async _connectorOperationArgWrapper =>
{
logManager.WriteLog($"Library : Sending the operation to connector service for {_connectorOperationArgWrapper.itemPresence.ItemName} on connector {connectorService.GetConnectorName(_connectorOperationArgWrapper.itemPresence.ConnectorId)}", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), LogLevel.Information);
LibraryItems.Single(l => l.CI.ID == _connectorOperationArgWrapper.ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == _connectorOperationArgWrapper.itemPresence.itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == _connectorOperationArgWrapper.itemPresence.ConnectorId).StatusString = "Cheking Presence";
LibraryItems.Single(l => l.CI.ID == _connectorOperationArgWrapper.ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == _connectorOperationArgWrapper.itemPresence.itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == _connectorOperationArgWrapper.itemPresence.ConnectorId).Status = LibraryItemState.UpdatingStatus;
await connectorService.EnqueueConnectorOperations(_connectorOperationArgWrapper.operationType, _connectorOperationArgWrapper.ci, _connectorOperationArgWrapper.itemPresence.itemId, Settings.Default.ProjectLocalPath + @"\" + _connectorOperationArgWrapper.ci.ID.ToString(), _connectorOperationArgWrapper.itemPresence.ConnectorId, Settings.Default.DisplayLanguage, Settings.Default.ProjectLocalPath, _connectorOperationArgWrapper.itemPresence).ConfigureAwait(false);
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1,
});
}
Итак, вызов методов по элементам, который теперь расширяется, выглядит следующим образом:
public async Task EnqueueConnectorOperations(ConnectorOperationArgWrapper _args)
{
logManager.WriteLog($"Library : Received operation request for {_args.itemPresence.ItemName} on connector {connectorService.GetConnectorName(_args.itemPresence.ConnectorId)}", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), LogLevel.Information);
if (_args.ci.CIType == EnumCIType.Application)
{
LibraryItems.Single(l => l.CI.ID == _args.ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == _args.itemPresence.itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == _args.itemPresence.ConnectorId).IsWorking = true;
LibraryItems.Single(l => l.CI.ID == _args.ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == _args.itemPresence.itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == _args.itemPresence.ConnectorId).Status = LibraryItemState.NeedsAttention;
LibraryItems.Single(l => l.CI.ID == _args.ci.ID).DeployableAssetMenuItems.Single(d => d.AssetId == _args.itemPresence.itemId).ConnectorsMenuItems.Single(c => c.ConnectorID == _args.itemPresence.ConnectorId).StatusString = "Waiting";
}
logManager.WriteLog($"Library : post actionblock", System.Threading.Thread.CurrentThread.ManagedThreadId.ToString(), LogLevel.Information);
await actionBlock.SendAsync(_args);
//actionBlock.Complete();
//await actionBlock.Completion;
}
Я прокомментировал часть с завершением и завершением actionBlock, потому что я хочу, чтобы блок мог получать и ставить в очередь запрос в любое время и даже, может быть, несколько раз за элемент.
Пока что это похоже на работу, правильно ли это делать, как я, или я столкнусь с некоторыми проблемами с этим?