【发布时间】:2019-01-21 15:35:58
【问题描述】:
我目前正在构建一个 MVVM 应用程序,我的一个视图模型使用通过依赖注入注册的服务。此服务针对各种第 3 方应用程序运行 powershell cmdlet 或 http REST 命令,这些应用程序在同时收到多个请求时不太满意。
这就是为什么我希望能够从 UI 触发多个操作(而不阻塞它),但要确保服务一次只处理一个。我的 UI 元素将同时显示它们是否正在工作或等待。
我尝试实现 TPL ActionBlock,但到目前为止,我所有的操作都同时运行,这是我发现让它们在队列中工作的唯一方法会阻塞 UI,直到所有任务都完成。
这是我所做的:
我的视图模型包含一个 ObservableCollection 元素,其中包含两个列表(一个嵌套在另一个列表中) 在 UI 上,它看起来像一个项目列表,可以展开以显示一个小树视图。
我想要的是,每次我展开一个项目时,树视图中的所有子项目都会通过服务检查它们在 3rd 方应用程序中的状态。 UI子项中的方法如下所示:
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”是第一级项目,“parent.Library”是托管所有内容的主视图模型。
在 View 模型上,检索 this 的方法如下:
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() 在最后一行,所有任务都会按顺序运行,我的 UI 会被阻止。
如果我改用 await actionBlock.Completion()。 UI 没有被阻塞,而是并行运行。
因此,如果有人能提供建议,那就太好了!
更新:
我调整了 JSteward 的 anwser 以满足我的需要:
我按照您的建议将 ActionBlock 声明为我的视图模型的私有成员。但是,当我按照您所说的那样使用一个项目时,它是正确排队的操作,但是如果我扩展另一个项目,那么它的操作(也在他们的队列中)与第一个项目的操作并行运行。无论请求多少项目,我都不希望一次只进行一次操作。
所以我做了以下更改: ActionBlock 在 viewmodel 的构造函数中被初始化一次:
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 complete 和 completion 评论了该部分,因为我希望该块能够随时接收和排队请求,甚至每个项目可能多次。
到目前为止,它似乎奏效了,我这样做是对的还是我会面临一些麻烦?
【问题讨论】:
-
您的更新完全符合我的预期,我以为您希望每个根项目开始一个批处理。你的修复就是我要编辑我的答案。在 ctor 中创建
ActionBlock并删除完成代码。我想你们现在都准备好了。只需确保在离开页面之前让块清除或取消即可。由于您一次只运行一个项目,请将取消令牌传递给块并在导航时取消它。 -
是的,在我决定今天该停下来之前有一个案例,其中一个操作失败并且不知何故动作块卡在运行状态。但我想通过更严格的错误控制,这将得到解决。
标签: c# asynchronous mvvm task-parallel-library tpl-dataflow