我有一个Web服务( ExternalWebService ),它接收一段时间(开始和结束日期)并返回此期间的所有日志,我想打电话给这项服务很长一段时间。问题是这个服务只允许每个请求发送少量数据,长时间意味着大量数据,导致错误的原因。因此,我决定循环遍历作为参数传递的时段,并使用任务并行调用此服务,在任务执行结束时连接结果。这是代码:
public List<object> GetList(DateTime start, DateTime end)
{
List<object> finalList = new List<object>();
object lockList = new object();
DateTime current = start;
List<Task> threads = new List<Task>();
do
{
current = new DateTime(Math.Min(current.AddMonths(1).Ticks, end.Ticks));
Task thread = Task.Run(() => {
List<object> partialList = ExternalWebService.GetListByPeriod(from: start, to: current);
lock (lockList)
{
finalList = finalList.Concat(partialList).ToList();
}
});
threads.Add(thread);
start = current;
}
while (current < end);
Task.WhenAll(threads).Wait();
return finalList;
}
此代码有效但结果意外,因为变量 start 和 current 在线程内使用之前发生了变化。那么,我该怎么做才能保证 Task.Run 中使用的 start 和当前日期具有与线程时相同的值创建
答案 0 :(得分:3)
最好不要分享和改变你的约会。您可以启动一组以异步方式查询Web服务并展平结果的任务。
public class GetData {
public async Task<IEnumerable<object>> GetDataAsync(DateTime startDate, DateTime endDate) {
var daysPerChunk = 28;
var totalChunks = (int)Math.Ceiling((endDate - startDate).TotalDays / daysPerChunk);
var chunks = Enumerable.Range(0, totalChunks);
var dataTasks = chunks.Select(chunkIndex => {
var start = startDate.AddDays(chunkIndex * daysPerChunk);
var end = new DateTime(Math.Min(start.AddDays(daysPerChunk).Ticks, endDate.Ticks));
return ExternalWebService.GetListByPeriodAsync(from: start, to: end);
});
var results = await Task.WhenAll(dataTasks);
var data = results.SelectMany(_ => _);
return data.ToList();
}
}
public class ExternalWebService {
private static HttpClient Client {
get;
} = new HttpClient();
public async static Task<IEnumerable<object>> GetListByPeriodAsync(DateTime from, DateTime to) {
var response = await Client.GetAsync("GetListByPeriodFromToUri");
if (response != null && response.IsSuccessStatusCode) {
using (var stream = await response.Content.ReadAsStreamAsync()) {
using (var reader = new StreamReader(stream)) {
var str = reader.ReadToEnd();
return JsonConvert.DeserializeObject<IEnumerable<object>>(str);
}
}
}
return Enumerable.Empty<object>();
}
}
答案 1 :(得分:0)
您可以创建一个方法来接收您想要的DateTime
并返回委托以将其传递给Task.Run
方法,例如:
private Action GetMethodAction(DateTime current)
{
return () => { /* your code here */ }
}
这样current
的值就会与你要返回的动作绑定。希望它有所帮助。
答案 2 :(得分:0)
以下代码对我有用:
protected override List<object> GetList(DateTime start, DateTime end)
{
List<object> list = new List<object>();
object lockList = new object();
DateTime current = start;
List<Task> threads = new List<Task>();
do
{
current = new DateTime(Math.Min(current.AddMonths(1).Ticks, end.Ticks));
Task thread = Task.Run(GetMethodFunc(start, current)).ContinueWith((result) =>
{
lock (lockList)
{
list = list.Concat(result.Result).ToList();
}
});
threads.Add(thread);
start = current;
}
while (current < end);
Task.WhenAll(threads).Wait();
return list;
}
private Func<List<object>> GetMethodFunc(DateTime start, DateTime end)
{
return () => {
List<object> partialList = ExternalWebService.GetListByPeriod(from: start, to: end);
return partialList;
};
}