Task.Factory.StartNew 内部方法有新参数时不启动

Task.Factory.StartNew does not start when method inside have a new parameter

(问题已解决) 我有一个 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我把一个方法放进去的时候,那个方法(对象)的参数必须声明在外面???因为当我像第一种情况那样写 shortly 时,我将 "new" 关键字放在参数中,而任务从不 运行。 原因:在行动中,我想 return 尽快查看,与该视图无关的任何其他内容将在任务中执行,客户端无需等待完成。

我很抱歉我的英语不好:)

更新 1: 谢谢 Panagiotis Kanavos, I used 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.

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

照原样,您的代码 return 会在任务完成前发送给调用者。到代码 return 时,任务甚至可能还没有开始执行,尤其是在调试该方法时。调试器冻结所有线程,一步一步只执行其中一个。

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

您应该改为编写一个适当的异步方法,仅当异步操作完成时才 return 向用户发送:

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 中使用 ExecuteNonQueryAsync if you are using ADO.NET or 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");
}

如果您希望操作在后台 运行 并立即 return 到客户端,您可以使用 QueueBackgroundWorkItem 启动一个新任务 and 将其注册到 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(ct 参数)的 CancellationTask 通知任务。如果任务没有及时完成,IIS 将继续并中止线程。一个长 运行ning 任务应该定期检查令牌。

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

Scott Hanselman 有一篇很好的文章描述了 运行g 任务可用的所有方法,甚至在 How to run Background Tasks in ASP.NET

中的后台调度作业