C# 如何在完成多个任务后继续执行而不阻塞UI线程?
在我的MVVM应用程序中,我的视图模型调用3种不同的服务方法,将每个方法中的数据转换为通用格式,然后使用属性通知/可观察集合等更新UI 服务层中的每个方法都启动一个新的C# 如何在完成多个任务后继续执行而不阻塞UI线程?,c#,wpf,mvvm,task-parallel-library,busyindicator,C#,Wpf,Mvvm,Task Parallel Library,Busyindicator,在我的MVVM应用程序中,我的视图模型调用3种不同的服务方法,将每个方法中的数据转换为通用格式,然后使用属性通知/可观察集合等更新UI 服务层中的每个方法都启动一个新的任务,并将任务返回到视图模型。下面是我的一个服务方法的示例 public class ResourceService { internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<
任务
,并将任务
返回到视图模型。下面是我的一个服务方法的示例
public class ResourceService
{
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
var t = Task.Factory.StartNew(() =>
{
//... get resources from somewhere
return resources;
});
t.ContinueWith(task =>
{
if (task.IsFaulted)
{
errorCallback(task.Exception);
return;
}
completedCallback(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
return t;
}
}
公共类资源服务
{
内部静态任务LoadResources(Action completedCallback、Action errorCallback)
{
var t=Task.Factory.StartNew(()=>
{
//…从某处获取资源
归还资源;
});
t、 继续(任务=>
{
if(task.IsFaulted)
{
errorCallback(task.Exception);
返回;
}
completedCallback(task.Result);
},TaskScheduler.FromCurrentSynchronizationContext());
返回t;
}
}
下面是调用代码和视图模型的其他相关部分
private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>();
public ICollectionView DataView
{
get { return _dataView; }
set
{
if (_dataView != value)
{
_dataView = value;
RaisePropertyChange(() => DataView);
}
}
}
private void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.WaitAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
private Task LoadResources()
{
return ResourceService.LoadResources(resources =>
{
foreach(var r in resources)
{
var d = convertResource(r);
Data.Add(d);
}
},
error =>
{
// do some error handling
});
}
私有ObservableCollection数据=新ObservableCollection();
公共ICollectionView数据视图
{
获取{return\u dataView;}
设置
{
如果(_dataView!=值)
{
_数据视图=值;
RaisePropertyChange(()=>DataView);
}
}
}
私有void LoadData()
{
SetBusy(“加载…”);
Data.Clear();
任务[]任务=新任务[3]
{
LoadTools(),
LoadResources(),
装卸工
};
Task.WaitAll(任务);
DataView=CollectionViewSource.GetDefaultView(数据);
DataView.Filter=FilterTimelineData;
IsBusy=false;
}
私有任务LoadResources()
{
返回ResourceService.LoadResources(资源=>
{
foreach(资源中的var r)
{
var d=资源(r);
数据.添加(d);
}
},
错误=>
{
//做一些错误处理
});
}
这几乎是可行的,但有几个小问题
第一:在一开始调用SetBusy
时,在我开始任何任务之前和调用WaitAll
之前,我将IsBusy
属性设置为true。这将更新UI并显示BusyIndicator控件,但它不起作用。我还尝试添加简单的字符串属性并绑定它们,但它们也没有被更新。IsBusy功能是基类的一部分,在其他视图模型中也可以使用,在这些视图模型中,我没有运行多个任务,因此我不认为XAML中的属性通知或数据绑定存在问题
所有的数据绑定似乎都是在整个方法完成后更新的。我没有在输出窗口中看到任何“首次异常”或绑定错误,这让我相信UI线程在调用WaitAll之前不知何故被阻塞了
第二:我似乎从服务方法返回了错误的任务。我希望在视图模型转换了回调中所有服务方法的所有结果之后运行WaitAll
之后的所有操作。但是,如果我从服务方法返回continuation任务,将永远不会调用continuation,并且WaitAll
将永远等待。奇怪的是,绑定到ICollectionView的UI控件实际上正确地显示了所有内容,我假设这是因为数据是一个可观察的集合,并且CollectionViewSource知道集合已更改的事件。您可以使用它构建一个在输入任务全部完成时运行的延续
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
Task.Factory.ContinueWhenAll(tasks, t =>
{
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}, CancellationToken.None, TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
请注意,如果使用C#5的await
/async
语法,这将变得更简单:
private async void LoadData()
{
SetBusy("Loading...");
Data.Clear();
Task[] tasks = new Task[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};
await Task.WhenAll(tasks);
DataView = CollectionViewSource.GetDefaultView(Data);
DataView.Filter = FilterTimelineData;
IsBusy = false;
}
但是,如果我从服务方法返回continuation任务,那么continuation将永远不会被调用,WaitAll将永远等待 问题是您的延续任务需要UI线程,并且您在
WaitAll
调用中阻塞了UI线程。这会造成无法解决的死锁
修复上面的问题应该可以纠正这一点-您希望将延续作为任务返回,因为这是您需要等待完成的任务-但是通过使用TaskFactory.continuewhalll
可以释放UI线程,以便它可以处理这些延续
注意,这是另一件用C#5简化的事情。您可以将其他方法编写为:
internal static async Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
try
{
await Task.Run(() =>
{
//... get resources from somewhere
return resources;
});
}
catch (Exception e)
{
errorCallback(task.Exception);
}
completedCallback(task.Result);
}
内部静态异步任务LoadResources(Action completedCallback,Action errorCallback)
{
尝试
{
等待任务。运行(()=>
{
//…从某处获取资源
归还资源;
});
}
捕获(例外e)
{
errorCallback(task.Exception);
}
completedCallback(task.Result);
}
也就是说,编写返回
任务的方法通常比提供回调要好,因为这样可以简化使用的两端。我一直推迟使用async/await,因为我不确定它是否“只适用于”WPF命令和Prism DelegateCommand。Task.ContinueWhenAll对我来说似乎不存在,我需要引用一些TPL扩展库吗?@BenCr抱歉-它是TaskFactory.ContinueWhenAll。一般来说,wait/async工具比WPF中的任务延续更有效。主要的区别是,你不必为了处理异常而跳出疯狂的圈套。感谢里德,这似乎工作得更好。希望有人能够在调用WaitAll之前对阻塞进行解释,但这肯定解决了问题1和2。我将在下一次迭代中尝试async/await的内容,看看我的进展如何。@BenCR我刚刚编辑了我的文章来解释它。基本上,WaitAll会在任务完成之前阻塞UI,但任务需要UI来完成;)@BenCr:Async/await比使用具有连续性的任务(以及来自同步上下文的任务调度器等)容易得多。如果需要async
命令,可以使用。