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

时间:2022-09-25 21:01:37

I have a web service (ExternalWebService) that receives a period (start and end date) and returns all logs for this period, and I want to make a call for this service passing a long period. The problem is that this service allows only a small amount of data to be sent per request, and long periods means large amount of data, what causes an error. So, I decided to loop through the months of the period passed as parameter and make calls for this service in parallel using tasks, concatenating the results at the end of the execution of the tasks. Here is the code:

我有一个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;
}

This code works but has an unexpected result, because the variables start and current change before being used inside the thread. So, what can I do to guarantee that the start and current date used inside Task.Run have the same values they had when the thread was created?

此代码有效,但有一个意外的结果,因为变量在线程内使用之前开始和当前更改。那么,我该怎么做才能保证Task.Run中使用的开始日期和当前日期与创建线程时的值相同?

3 个解决方案

#1


0  

You can create a method that receives the DateTime you want and return the delegate to pass it to the Task.Run method, something like:

您可以创建一个接收所需DateTime的方法,并返回委托以将其传递给Task.Run方法,如:

private Action GetMethodAction(DateTime current) 
{
     return () => { /* your code here */ }
}

That way the value of current is bound to the action you are returning. Hope it helps.

这样,current的值就会绑定到您要返回的操作。希望能帮助到你。

#2


3  

It would be best not to both share and mutate your dates. You can spin up a set of tasks that query your web service asynchronously and flatten the results.

最好不要分享和改变你的约会。您可以启动一组任务,以异步方式查询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>();
    }
}

#3


0  

Here is the code that worked for me:

这是适合我的代码:

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

#1


0  

You can create a method that receives the DateTime you want and return the delegate to pass it to the Task.Run method, something like:

您可以创建一个接收所需DateTime的方法,并返回委托以将其传递给Task.Run方法,如:

private Action GetMethodAction(DateTime current) 
{
     return () => { /* your code here */ }
}

That way the value of current is bound to the action you are returning. Hope it helps.

这样,current的值就会绑定到您要返回的操作。希望能帮助到你。

#2


3  

It would be best not to both share and mutate your dates. You can spin up a set of tasks that query your web service asynchronously and flatten the results.

最好不要分享和改变你的约会。您可以启动一组任务,以异步方式查询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>();
    }
}

#3


0  

Here is the code that worked for me:

这是适合我的代码:

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