如何运行使用相同外部变量的任务?

时间:2017-03-17 19:28:44

标签: c# .net multithreading task-parallel-library

我有一个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 当前日期具有与线程时相同的值创建

3 个答案:

答案 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;
    };
}