0

(问题已解决)我有一个 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中放入一个方法时,该方法(对象)的参数必须在外面声明???因为当我像第一种情况一样写的时候,我把“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"],在这种情况下,当我调用异步方法时,当该方法实际执行时,它可以访问为null的HttpContext.Current。所以我可以通过在外部声明对象来存储数据并将其传递给方法来解决这个问题,或者我捕获 UserModel.Current 并将其传递给这个方法以使用 UserModel.Current.UserId。

我的问题实际上解决了,感谢大家帮助我,尤其是Panagiotis Kanavos

4

2 回答 2

0

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

于 2015-03-10T08:15:25.067 回答
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伪造异步执行也不是一个好主意——您只需将执行从一个服务器线程切换到另一个。您应该使用异步方法一直到数据库,例如。如果您在实体框架中使用 ADO.NET 或SaveChangesAsync ,请使用ExecuteNonQueryAsync 。这样,在代码等待数据库调用完成时,没有线程执行或阻塞。

编译器还负责捕获字段的值,因此您无需复制任何内容。生成的代码更干净:

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(参数)的CancellationTask通知任务。ct如果任务没有及时完成,IIS 将继续并中止线程。长时间运行的任务应定期检查令牌。

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

Scott Hanselman 有一篇很好的文章,在How to run Background Tasks in ASP.NET中描述了可用于在后台运行任务甚至计划作业的所有方法

于 2015-03-10T08:37:34.317 回答