在我的MVVM应用程序中,我的视图模型调用3种不同的服务方法,将每种方法的数据转换为通用格式,然后使用属性通知/可观察集合等更新UI。
服务层中的每个方法都会启动一个新的Task
并将Task
返回给视图模型。以下是我的一种服务方法的示例。
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;
}
}
这是调用代码和视图模型的其他相关部分......
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
});
}
这几乎可行,但有一些小问题。
编号1:在开始调用SetBusy
时,在我开始执行任务之前和调用WaitAll
之前,我将IsBusy
属性设置为true。这应该更新UI并显示BusyIndicator控件,但它不起作用。我也尝试添加简单的字符串属性并绑定它们,它们也没有更新。 IsBusy功能是基类的一部分,适用于其他视图模型,我没有运行多个任务,因此我不认为XAML中的属性通知或数据绑定存在问题。
整个方法完成后,所有数据绑定似乎都会更新。我没有在输出窗口中看到任何“第一次出现异常”或绑定错误,这导致我相信在调用WaitAll之前UI线程以某种方式被阻止。
2号:我似乎从服务方法返回了错误的任务。我希望在视图模型转换回调中所有服务方法的所有结果后,WaitAll
之后的所有内容都会运行。但是,如果我从service方法返回continuation任务,则永远不会调用continuation,WaitAll
将永远等待。奇怪的是绑定到ICollectionView的UI控件实际上正确显示了所有内容,我认为这是因为Data是一个可观察的集合,而CollectionViewSource知道集合已更改的事件。
答案 0 :(得分:9)
您可以使用TaskFactory.ContinueWhenAll构建一个在输入任务全部完成时运行的延续。
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;
}
但是,如果我从service方法返回continuation任务,则永远不会调用continuation并且WaitAll将永远等待
问题是您的延续任务需要UI线程,并且您正在阻止WaitAll
调用中的UI线程。这会产生无法解决的死锁。
修正上述内容应该纠正这一点 - 你需要将Continuation作为任务返回,因为这是你需要等待完成的东西 - 但是通过使用TaskFactory.ContinueWhenAll
你可以释放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);
}
话虽这么说,通常最好编写方法来返回Task<T>
而不是提供回调,因为这简化了使用的两端。