2级任务.ContinueWith出现意外行为

4

I have some code that lays out like this:

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中的任务每次返回一个空列表,因为它没有等待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);
                    }
                });
            });

3
你的问题不是很清楚。Class2.method 指向 task2 方法吗? - Soner Gönül
1
如果您包含实际的代码,那么问题会更加清晰明了。 - svick
感谢指出这一点,Soner。我已更新伪代码。Svick,我知道真实的代码很好,但这里的问题不在语法上,而是在设计上,因此我觉得通过在这里描述伪代码来最小化沟通问题所需的行数更清晰。 - Andrew B Schultz
你说得对,svick - 当有人建议真正的代码时,通常都是这样。我已经添加了它。 - Andrew B Schultz
2个回答

8
这是预期的吗?有什么方法可以让我的class1.task1 continueblock等待我的class2.task2 continueblock吗?
当然可以,只需在第二个任务的继续操作上调用ContinueWith,而不是在第一个任务上调用。
如果需要等到两者都完成,可以使用Task.Factory.ContinueWhenAll

谢谢你的回答,Servy。但是你如何在另一个ContinueWith中编写ContinueWith呢?当两个ContinueWith块位于不同的类中时,它是否有效?我基本上需要Class1.Method1.Task1调用Class2.Method2,该方法包含一个Task,在其ContinueWith块(ContinueWithBlock2)中执行HttpWebRequest并获取响应,然后在完成Task2.ContinueWithBlock2之后执行Task1.ContinueWith。我已更新我的帖子以澄清这一点。 - Andrew B Schultz
1
@AndrewBSchultz 当然可以,你可以无限制地链接ContinueWith。ContinueWith会返回另一个任务,你可以在其上继续执行。只需从其他类中返回要添加连续性的任务,并在第一个方法中使用该返回值即可。 - Servy
1
@AndrewBSchultz,你应该返回一个Task<List<T>>而不是一个在未来某个不确定的时间点才会被填充的列表。实际上,你的代码永远都不会起作用。在你的延续结束时,你没有向列表中添加任何项,而只是将一个新列表分配给变量,但旧列表已经被返回,因此调用方法的人将永远看不到从异步操作返回的项。如果你返回一个Task<List<T>>,这个问题就解决了。 - Servy
不,我明白了。你是对的,JsonConvert.DeserializeObjecte<List<T>>()会将一个新值重新赋给变量resultList。我理解你的意思。问题在于Continuation和InvokeTaskMagic方法中的return语句之间存在竞争条件,而return语句总是获胜。 - Andrew B Schultz
谢谢Servy - 就这样了。我会用新代码更新我的OP。 - Andrew B Schultz
显示剩余4条评论

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接