来自2级Task.ContinueWith的意外行为

时间:2012-12-19 15:27:40

标签: c# task-parallel-library

我有一些代码如下:

的Class1

Task<List<ConfSession>> getSessionsTask = Task.Factory.StartNew(() =>
            {
                var confSessions = 
                    TaskHelper<ConfSession>.InvokeTaskMagic(request);
                //PROBLEM - THIS CODE RACES TO THE NEXT LINE 
                //BEFORE confSessions IS POPULATED FROM THE LINE ABOVE - IE 
                //confSessions IS ALWAYS AN EMPTY LIST 
                //WHEN IT'S RETURNED
                return confSessions;
            }
        );

Class2(TaskHelper)

//methods
public static List<T> InvokeTaskMagic(HttpWebRequest request)
{
    var resultList = new List<T>();

    Task<WebResponse> task1 = Task<WebResponse>.Factory.FromAsync(
        (callback, o) => ((HttpWebRequest)o).BeginGetResponse(callback, o)
        , result => ((HttpWebRequest)result.AsyncState).EndGetResponse(result)
        , request);

    task1.ContinueWith((antecedent) =>
    {
        if (antecedent.IsFaulted)
        {
            return;
        }

        WebResponse webResponse;
        try
        {
            webResponse = task1.Result;
        }
        catch (AggregateException ex1)
        {
            throw ex1.InnerException;
        }

        string responseString;

        using (var response = (HttpWebResponse)webResponse)
        {
            using (Stream streamResponse = response.GetResponseStream())
            {
                StreamReader reader = new StreamReader(streamResponse);
                responseString = reader.ReadToEnd();
                reader.Close();
            }
        }

        if (responseString != null)
        {
            resultList = 
               JsonConvert.DeserializeObject<List<T>>(responseString);
        }
    });

    return resultList;
}

TaskHelper是我创建的一个类,用于标准化我在几种方法中使用的一堆冗余任务代码。它仅用于获取HttpWebRequest,在Task中执行它,在ContinueWith块中获取响应,将响应解析为List<T>,然后返回List<T>

我在一个任务中包装了Class1对TaskHelper的调用,因为我需要在继续class1之前从InvokeTaskMagic方法获取结果(即class1中的下一步是使用List<T>。正如它在在代码中的注释,我的问题是class1中的Task每次都返回一个空列表,因为它不等待TaskHelper类中的InvokeTaskMagic方法的响应。

这是预期的吗?有没有办法使getSessionsTask等待返回,直到TaskHelper.InvokeTaskMagic返回?

更新: 工作代码如下 - 感谢Servy的帮助。

public static class TaskHelper<T> where T : class
{
    //methods
    public static Task<List<T>> InvokeTaskMagic(HttpWebRequest request)
    {
        var resultList = new List<T>();

        Task<WebResponse> task1 = Task<WebResponse>.Factory.FromAsync(
            (callback, o) => ((HttpWebRequest)o).BeginGetResponse(callback, o)
            , result => ((HttpWebRequest)result.AsyncState).EndGetResponse(result)
            , request);

        return task1.ContinueWith<List<T>>((antecedent) =>
        {
            if (antecedent.IsFaulted)
            {
                return new List<T>();
            } 

            WebResponse webResponse;
            try
            {
                webResponse = task1.Result;
            }
            catch (AggregateException ex1)
            {
                throw ex1.InnerException;
            }

            string responseString;

            using (var response = (HttpWebResponse)webResponse)
            {
                using (Stream streamResponse = response.GetResponseStream())
                {
                    StreamReader reader = new StreamReader(streamResponse);
                    responseString = reader.ReadToEnd();
                    reader.Close();
                }
            }

            if (responseString != null)
            {
                return JsonConvert.DeserializeObject<List<T>>(responseString);
            }
            else
            {
                return new List<T>();
            }
        }); 
    }
}

调用InvokeTaskMagic方法如下:

var getCommentsAboutTask = Task.Factory.StartNew(() =>
            {
                var comments = TaskHelper<Comment>.InvokeTaskMagic(request);
                return comments;
            });

        getCommentsAboutTask.ContinueWith((antecedent) =>
            {
                if (antecedent.IsFaulted)
                { return; }

                var commentList = antecedent.Result.Result;
                UIThread.Invoke(() =>
                {
                    foreach (Comment c in commentList)
                    {
                        AllComments.Add(c);
                        GetCommentRelatedInfo(c);
                    }
                });
            });

2 个答案:

答案 0 :(得分:8)

  

这是预期的吗?有没有办法让我的class1.task1 continueblock等待我的class2.task2 continueblock?

当然,只需在第二个任务的延续而不是第一个任务上调用ContinueWith

如果您需要等到两者都完成,您可以使用Task.Factory.ContinueWhenAll

答案 1 :(得分:1)

我前段时间做过与你的代码类似的事情,很难让它在错误处理等方面正常工作。

首先,我创建了一个泛型方法来处理具有不同返回类型的延续

private AsyncCallback EndAsync<T1, T2>(TaskCompletionSource<T2> tcs,
                                       Func<IAsyncResult, T1> endMethod, 
                                       Func<T1, T2> continueWith) {
    return ar => {
        T1 result1;
        try {
            result1 = endMethod(ar);
        }
        catch (Exception err) {
            tcs.SetException(err);
            return;
        }

        try {
            T2 result2 = continueWith(result1);
            tcs.SetResult(result2);
        }
        catch (Exception err) {
            tcs.SetException(err);
            return;
        }
    };
}

然后我创建这样的异步任务:

public Task<List<T>> GetDataAsync(IProxy proxy) {
    var tcs = new TaskCompletionSource<List<T>>();
    var asyncCallback = EndAsync(tcs, 
                                 proxy.EndGetData, 
                                 result => result != null ? 
                                           ProcessResult(result) : 
                                           new List<T>());
    proxy.BeginGetData(asyncCallback);

    return tcs.Task;
}