当内部方法有一个新参数时,Task.Factory.StartNew不会启动

时间:2015-03-10 07:50:39

标签: asp.net-mvc taskfactory

(问题解决了) 我的行动中有一个MVC应用程序:

第一种情况:任务从未开始。

public ActionResult Insert(NewsManagementModel model) {
            //Do some stuff

            //Insert history  
            //new object NewsHistoryDto  as the parameter
            Task.Factory.StartNew(() => InsertNewsHistory(new NewsHistoryDto {
                UserId = 1234,
                Time = DateTime.Now,
                Id = 1234
            })); 
            return RedirectToAction("Index", "NewsManagement");
        }

第二种情况:任务正常运行

public ActionResult Insert(NewsManagementModel model) {
            //Do some stuff

            //Insert history 
            //object NewsHistoryDto was declared outside
            var history = new NewsHistoryDto {
                UserId = 1234,
                Time = DateTime.Now,
                Id = 1234
            }; 
            Task.Factory.StartNew(() => InsertNewsHistory(history)); 
            return RedirectToAction("Index", "NewsManagement");
        }

我的问题是:当Task.Factory.StartNew和我把一个方法放入其中时,该方法(对象)的参数必须在外面声明???因为当我像第一个案例一样写短片时,我把'#34; new"参数中的关键字和任务永远不会运行。 原因:在行动中,我希望尽快返回视图,任何其他与该视图无关的内容都将在任务中执行,客户端无需等待完成。

我非常抱歉我的英语不好:)

更新1 : 谢谢Panagiotis Kanavos,我使用QueueBackgroundWorkItem,但问题仍然相同,如果我将对象声明为外,此方法正常运行。但是当我在参数中使用new关键字时,此方法永远不会运行。没有例外,没有错误。任何人都可以向我解释这是如何可能的:(

更新2 : 我试了两个案例:

首先:

    HostingEnvironment.QueueBackgroundWorkItem(delegate {
        var handler = m_bussinessHandler;
        handler.InsertNewsHistoryAsync(new NewsHistoryDto {
            UserId = UserModel.Current.UserId,
            Time = DateTime.Now,
            Id = newsId
        });
    });-> still doens't works

第二

        var history = new NewsHistoryDto {
            UserId = UserModel.Current.UserId,
            Time = DateTime.Now,
            Id = newsId
        };

        HostingEnvironment.QueueBackgroundWorkItem(delegate {
            var handler = m_bussinessHandler;
            handler.InsertNewsHistoryAsync(history);
        });-> works normally

那么这里的问题在哪里?这不是关于m_bussinessHandler,因为我复制了。

更新3 :我找到了原因。 原因是UserModel.Current,这是HttpContext.Current.Session["UserModel"]中的一个对象,在这种情况下当我调用异步方法时,当这个方法实际执行时,它可以访问HttpContext.Current,它是null。所以我可以通过声明对象外部存储数据并将其传递给方法来解决这个问题,或者我捕获UserModel.Current并将其传递给此方法以使用UserModel.Current.UserId。

我的问题实际上已经解决了,感谢大家帮助我,特别是Panagiotis Kanavos

2 个答案:

答案 0 :(得分:0)

您的m_bussinessHandler是一个实例字段吗?因为它可以在你完成动作后被处理掉。

答案 1 :(得分:0)

实际上,您的代码在任务完成之前返回给调用者。在代码返回时,任务可能甚至没有开始执行,尤其是在调试方法时。调试器冻结所有线程并逐步执行其中一个。

此外,.NET的参考捕获意味着当您使用m_businessHandler时,您将引用捕获到m_businessHandler字段,而不是其值。如果您的控制器收集了垃圾,这将导致NullReferenceException。为了避免这种情况,你必须在lambda中复制字段的值。

您应该编写一个适当的异步方法,仅在异步操作完成时返回给用户:

public async Task<ActionResult> Insert(NewsManagementModel model) {
    //Do some stuff

    //Insert history  
    //new object NewsHistoryDto  as the parameter
    await Task.Run(() => 
        var handler=m_bussinessHandler;
        handler.InsertNewsHistory(new NewsHistoryDto {
            UserId = 1234,
            Time = DateTime.Now,
            Id = 1234
    })); 
    return RedirectToAction("Index", "NewsManagement");
}

Task.RunTask.Factory.StartNew大致相同。

即便如此,使用Task.Run伪造异步执行并不是一个好主意 - 您只需将执行从一个服务器线程切换到另一个服务器线程。您应该使用异步方法一直到数据库,例如。如果您在Entity Framework中使用ADO.NET或ExecuteNonQueryAsync,请使用SaveChangesAsync。这样,在代码等待数据库调用完成时,没有线程执行或阻塞。

编译器还负责捕获字段的值,因此您无需复制任何内容。结果代码更清晰:

public async Task<ActionResult> Insert(NewsManagementModel model) {
    //Do some stuff

    //Insert history  
    //new object NewsHistoryDto  as the parameter
    await m_bussinessHandler.InsertNewsHistoryAsync(new NewsHistoryDto {
            UserId = 1234,
            Time = DateTime.Now,
            Id = 1234
    }; 
    return RedirectToAction("Index", "NewsManagement");
}

如果您确实希望操作在后台运行并立即返回客户端,则可以使用QueueBackgroundWorkItem启动新的任务将其注册到IIS。在这种情况下,您必须再次复制字段的值:

public ActionResult Insert(NewsManagementModel model) {
    //Do some stuff

    //Insert history  
    //new object NewsHistoryDto  as the parameter
    HostingEnvironment.QueueBackgroundWorkItem(ct=>
        var handler=m_bussinessHandler;
        handler.InsertNewsHistoryAsync(new NewsHistoryDto {
            UserId = 1234,
            Time = DateTime.Now,
            Id = 1234
    }); 
    return RedirectToAction("Index", "NewsManagement");
}

IIS仍然可以取消该任务,例如,当应用程序池回收时。在它执行此操作之前,它将通过传递给lambda的CancellationTaskct参数)通知任务。如果任务没有及时完成,IIS将继续并中止该线程。长时间运行的任务应定期检查令牌。

您可以将取消令牌传递给大多数异步方法,例如ExecuteNonQueryAsync(CancellationToken)。这将导致IO操作在安全的情况下立即取消,而不是等到远程服务器响应。

斯科特·汉塞尔曼(Scott Hanselman)有一篇很好的文章描述了在How to run Background Tasks in ASP.NET

中在后台运行任务甚至预定工作的所有方法