在C#中如何等待任务完成?

68
我想向服务器发送请求并处理返回的值:
private static string Send(int id)
{
    Task<HttpResponseMessage> responseTask = client.GetAsync("aaaaa");
    string result = string.Empty;
    responseTask.ContinueWith(x => result = Print(x));
    responseTask.Wait(); // it doesn't wait for the completion of the response task
    return result;
}

private static string Print(Task<HttpResponseMessage> httpTask)
{
    Task<string> task = httpTask.Result.Content.ReadAsStringAsync();
    string result = string.Empty;
    task.ContinueWith(t =>
    {
        Console.WriteLine("Result: " + t.Result);
        result = t.Result;
    });
    task.Wait();  // it does wait
    return result;
}

我是否正确使用了Task?我认为不是,因为每次Send()方法都返回string.Empty,而Print返回正确的值。

我做错了什么?如何从服务器获取正确的结果?

6个回答

51
您的Print方法可能需要等待续体完成(ContinueWith返回一个任务,您可以等待它)。否则,在第二个ReadAsStringAsync完成之前,该方法将返回(在续体中分配结果之前)。您的send方法存在相同的问题。两者都需要等待续体以始终获得所需的结果。与下面类似。
private static string Send(int id)
{
    Task<HttpResponseMessage> responseTask = client.GetAsync("aaaaa");
    string result = string.Empty;
    Task continuation = responseTask.ContinueWith(x => result = Print(x));
    continuation.Wait();
    return result;
}

private static string Print(Task<HttpResponseMessage> httpTask)
{
    Task<string> task = httpTask.Result.Content.ReadAsStringAsync();
    string result = string.Empty;
    Task continuation = task.ContinueWith(t =>
    {
        Console.WriteLine("Result: " + t.Result);
        result = t.Result;
    });
    continuation.Wait();  
    return result;
}

7
顺便提一下,你先调用异步方法再立即等待它的模式,基本上和同步调用没什么区别。 - Kenneth Ito
当您被困在 .NET Framework 4.0 中并且添加 microsoft.bcl.async nuget pkg 以使用 Await 时遇到问题时,这是最佳解决方案。 - Vidiya Prasanth Pappannan

12

它等待 client.GetAsync("aaaaa");,但不等待 result = Print(x)

尝试使用 responseTask.ContinueWith(x => result = Print(x)).Wait()

--编辑--

Task responseTask = Task.Run(() => { 
    Thread.Sleep(1000); 
    Console.WriteLine("In task"); 
});
responseTask.ContinueWith(t=>Console.WriteLine("In ContinueWith"));
responseTask.Wait();
Console.WriteLine("End");

上述代码无法保证输出结果:
In task
In ContinueWith
End

但是这个操作会影响到 newTask,请注意。
Task responseTask = Task.Run(() => { 
    Thread.Sleep(1000); 
    Console.WriteLine("In task"); 
});
Task newTask = responseTask.ContinueWith(t=>Console.WriteLine("In ContinueWith"));
newTask.Wait();
Console.WriteLine("End");

但我在Print()方法中调用了task.Wait()。 - user266003
当您调用 task.Wait() 时,您等待的是原始的 Task,而不是使用 ContinueWith 创建的那个。 - L.B
为什么你不在 newTask.Task() 之前调用 responseTask.Wait() - user266003
@OskarK.,无需等待上一个任务完成。ContinueWith将确保前一个任务已经完成。 - Sinatr

5
一个简洁的例子回答标题
string output = "Error";
Task task = Task.Factory.StartNew(() =>
{
    System.Threading.Thread.Sleep(2000);
    output = "Complete";
});

task.Wait();
Console.WriteLine(output);

谢谢,这帮助我解决了我的问题。 - Yesyoor

0
async Task<int> AccessTheWebAsync()  
{   
    // You need to add a reference to System.Net.Http to declare client.  
    HttpClient client = new HttpClient();  

    // GetStringAsync returns a Task<string>. That means that when you await the  
    // task you'll get a string (urlContents).  
    Task<string> getStringTask = 

    client.GetStringAsync("http://msdn.microsoft.com");  

    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  

    // The await operator suspends AccessTheWebAsync.  
    //  - AccessTheWebAsync can't continue until getStringTask is complete.  
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
    //  - Control resumes here when getStringTask is complete.   
    //  - The await operator then retrieves the string result from 
    getStringTask.  
    string urlContents = await getStringTask;  

    // The return statement specifies an integer result.  
    // Any methods that are awaiting AccessTheWebenter code hereAsync retrieve the length 
    value.  
    return urlContents.Length;  
}  

0

我是一名异步编程的初学者,所以不能确定这里到底发生了什么。尽管方法内部使用了任务,但我怀疑执行期望存在不匹配的情况。如果你将Print改为返回一个Task<string>,我认为你会得到期望的结果:

private static string Send(int id)
{
    Task<HttpResponseMessage> responseTask = client.GetAsync("aaaaa");
    Task<string> result;
    responseTask.ContinueWith(x => result = Print(x));
    result.Wait();
    responseTask.Wait(); // There's likely a better way to wait for both tasks without doing it in this awkward, consecutive way.
    return result.Result;
}

private static Task<string> Print(Task<HttpResponseMessage> httpTask)
{
    Task<string> task = httpTask.Result.Content.ReadAsStringAsync();
    string result = string.Empty;
    task.ContinueWith(t =>
    {
        Console.WriteLine("Result: " + t.Result);
        result = t.Result;
    });
    return task;
}

0
在处理continuations时,我发现将写入.ContinueWith的位置视为立即继续执行到其后面的语句的位置而不是“内部”语句的位置非常有用。在这种情况下,您将在Send中获得返回的空字符串。如果您对响应的唯一处理是将其写入控制台,则不需要Ito解决方案中的任何等待-控制台打印将在没有等待的情况下发生,但在这种情况下,Send和Print都应返回void。在控制台应用程序中运行此代码,您将获得页面的打印输出。
在我看来,等待和Task.Result调用(阻塞)有时是必要的,具体取决于所需的控制流,但更常见的是它们表明您没有正确使用异步功能。
namespace TaskTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Send();
            Console.WriteLine("Press Enter to exit");
            Console.ReadLine();
        }

        private static void Send()
        {
            HttpClient client = new HttpClient();
            Task<HttpResponseMessage> responseTask = client.GetAsync("http://google.com");
            responseTask.ContinueWith(x => Print(x));
        }

        private static void Print(Task<HttpResponseMessage> httpTask)
        {
            Task<string> task = httpTask.Result.Content.ReadAsStringAsync();
            Task continuation = task.ContinueWith(t =>
            {
                Console.WriteLine("Result: " + t.Result);
            });
        }
    }
}

对于非控制台应用程序?没有可用的Readline,有什么解决方案吗? - Kiquenet

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