【问题标题】:Task.ContinueWith execution orderTask.ContinueWith 执行顺序
【发布时间】:2012-05-08 16:12:50
【问题描述】:

显然,我不明白如何使用 ContinueWith 方法。我的目标是执行一项任务,完成后返回一条消息。

这是我的代码:

    public string UploadFile()
    {
        if (Request.Content.IsMimeMultipartContent())
        {
            //Save file
            MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
            Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);

            string filename = "Not set";

            task.ContinueWith(o =>
            {
                //File name
                filename = provider.BodyPartFileNames.First().Value;
            }, TaskScheduler.FromCurrentSynchronizationContext()); 

            return filename;
        }
        else
        {
            return "Invalid.";
        }
    }

变量“文件名”总是返回“未设置”。似乎从未调用 ContinueWith 方法中的代码。 (如果我在 VS 中逐行调试,它确实会被调用。)

在我的 ASP.NET Web API 控制器/Ajax POST 中调用此方法。

我在这里做错了什么?

【问题讨论】:

  • 这是因为您正在执行异步操作。
  • 另外,除了任务是异步的之外,我认为它们甚至都没有启动。

标签: c# task asp.net-web-api


【解决方案1】:

如果您使用的是异步操作,最好的方法是使您的操作也异步,否则您将失去正在进行的异步调用的优势。尝试如下重写您的方法:

public Task<string> UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        //Save file
        MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
        Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);

        return task.ContinueWith<string>(contents =>
        {
            return provider.BodyPartFileNames.First().Value;
        }, TaskScheduler.FromCurrentSynchronizationContext()); 
    }
    else
    {
        // For returning non-async stuff, use a TaskCompletionSource to avoid thread switches
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
        tcs.SetResult("Invalid.");
        return tcs.Task;
    }
}

【讨论】:

  • 哇,谢谢!这对我有用。也感谢其他所有人 - 感谢您的所有帮助。
  • 我有一个关于 TaskScheduler.FromCurrentSynchronizationContext 的问题 - 我的理解是这确实是一种 UI 方法(与 UI 线程同步),但我想知道是否有任何原因可以在 webapi 中使用方法。有吗?
  • 同步上下文也可用于存储一些线程局部变量,并确保在控件返回继续时重新填充它们。一个例子是Thread.CurrentPrincipal。如果我没记错的话,ASP.NET 运行时也为这种情况定义了一个同步上下文。
  • 如果您使用的是 .Net 4.0 - 请参阅此线程:stackoverflow.com/questions/15201255/…
【解决方案2】:

你的变量没有被设置的原因是:

  • 任务已实例化,但未运行。
  • 即使任务运行,函数可能会在它们完成运行之前返回,所以它仍然会返回“未设置”。解决此问题的方法是等待最终任务(一个设置 fileName)完成。

您的代码可以这样修复:

public string UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        //Save file
        MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
        Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);

        string filename = "Not set";

        var finalTask = task.ContinueWith(o =>
            {
                //File name
                filename = provider.BodyPartFileNames.First().Value;
            }, TaskScheduler.FromCurrentSynchronizationContext()); 

        task.Start();

        finalTask.Wait();

        return filename;
    }
    else
    {
        return "Invalid.";
    }
}

新增内容如下:

  • task.ContinueWith 的返回值分配给名为finalTask 的变量。我们需要这个任务,因为我们会等待它完成
  • 开始任务(task.Start(); 行)
  • 等待最终任务完成后再返回 (finalTask.Wait();)

如果可能,请考虑不要异步实现它,因为最终它是同步的(您正在等待它完成)并且当前的实现增加了可能可以避免的复杂性。

考虑按照这些思路做一些事情(如果可能的话):

public string UploadFile()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        //Save file
        MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));

        Request.Content.ReadAsMultipart(provider); // don't know if this is really valid.

        return provider.BodyPartFileNames.First().Value;
    }
    else
    {
        return "Invalid.";
    }
}

免责声明:我并没有实际执行上述代码;我只是写它来说明应该做什么。

【讨论】:

  • 你不应该调用Wait(),因为它会导致线程阻塞。相反,这个UploadFile() 函数本身应该是异步的。
  • 我尝试了您的第一个建议,但在启动方法上出现错误:可能不会在具有空操作的任务上调用启动。关于非异步 - 我不确定。我不认为有这样一种方法叫做 ReadAsMultipart (AFAIK)。
  • @marcind 你是对的,这是一个尴尬的结构。这就是为什么我建议尽可能使用同步替代方案。那是因为我们有两个任务是按顺序执行的,当它们完成时我们需要控制它们——我认为这里没有什么异步的。
  • @Rivka 是的,我没有说它有效,我只是建议您尝试找到一个更简单的替代方案。不需要异步代码,在这种情况下只会增加复杂性。
【解决方案3】:

您应该从方法中返回类型 Task&lt;T&gt;,在这种情况下,它将是 Task&lt;string&gt;

【讨论】:

  • 我试过了,但我遇到了同样的问题 - 没有设置“文件名”。
【解决方案4】:

您正在使用异步操作。如果你想等待它完成,你必须使用Wait 方法,否则你的任务:

task.ContinueWith(o =>
        {
            //File name
            filename = provider.BodyPartFileNames.First().Value;
        ).Wait();

return filename;

编辑: 一些异步方法在创建任务后立即启动,而另一些则要求您显式启动它们。您必须查阅每个文档以确定。在这种情况下,任务似乎会自动启动。

【讨论】:

  • 试过这个。使用 Wait() 似乎没有返回任何内容(甚至“未设置”)。
  • 那么这意味着任务在创建时没有运行。我相应地编辑了我的答案。
  • 获取错误 Start 可能不会在具有 null 操作的任务上调用。任务状态为“RanToCompletion”。
  • 这意味着任务已经开始(并完成......)。继续应该立即开始。如果没有,则使用的调度程序可能无法安排继续。我会尝试从 ContinueWith 调用中删除调度程序。
  • Wait 在这种情况下会阻塞线程,因此不会执行继续。
猜你喜欢
  • 1970-01-01
  • 2014-10-20
  • 2011-10-10
  • 2012-11-23
  • 2018-08-12
  • 2013-11-12
  • 2012-04-10
  • 2013-04-11
  • 2017-02-10
相关资源
最近更新 更多