【问题标题】:Entity Framework and Process.Start prevents async method call from executingEntity Framework 和 Process.Start 阻止异步方法调用执行
【发布时间】:2020-12-15 18:29:31
【问题描述】:
private void button1_Click(object sender, EventArgs e)
{
    // Comment out the following line and the StartProcess Method works fine
    List<SimpleTestTable> list = new PlainTestDBEntities().SimpleTestTables.ToList();

    new Thread(StartProcess).Start();

    while (true)
    { }
}

private void StartProcess()
{
    Thread.Sleep(2000);

    Console.WriteLine("I am executed totally fine.");

    Process.Start("calc.exe");

    Console.WriteLine("I am never executed if Entities are loaded in the calling method.");
}

由于某种原因,当从给定示例中的数据库加载列表时,StartProcess 方法将在调用Process.Start("calc.exe"); 之后挂起。所以计算器会出现,但随后执行停止。如果我注释掉数据库加载行,一切正常。

有什么想法吗?

谢谢。

更新: 显然我关于如何复制的说明不够清楚。我希望这会有所帮助:

  1. 运行以下脚本或手动执行,这将在 SQL Server 中创建一个名为 PlainTestDB 的数据库,添加一个名为 SimpleTestTable 的表,该表有两列。第一个 ID 带有 bigint 和 primarykey 标识,第二个 Name nvarchar(50),添加 3 个值 A、B 和 C。

重要提示:如果您运行脚本,请确保 CREATE DATABASE 语句之后的 go 语句位于下一行。

    use master
go

CREATE DATABASE [PlainTestDB] CONTAINMENT = NONE ON PRIMARY ( NAME = N'PlainTestDB', FILENAME = N'C:\Data\PlainTestDB.mdf' , SIZE = 8192KB , MAXSIZE = UNLIMITED, FILEGROWTH = 65536KB ) LOG ON ( NAME = N'PlainTestDB_log', FILENAME = N'C:\Data\PlainTestDB_log.ldf' , SIZE = 8192KB , MAXSIZE = 2048GB , FILEGROWTH = 65536KB ) WITH CATALOG_COLLATION = DATABASE_DEFAULT 
GO

use [PlainTestDB]

CREATE TABLE [dbo].[SimpleTestTable]( [ID] [bigint] IDENTITY(1,1) NOT NULL, [Name] nvarchar NOT NULL, CONSTRAINT [PK_SimpleTestTable] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY]

USE [PlainTestDB]

INSERT INTO [dbo].[SimpleTestTable] ([Name]) VALUES ('A'),('B'),('C')
  1. 在您的 Visual Studio 中创建一个新的 Windows 窗体应用程序。并添加一个带有点击处理程序的 Button。

  2. 向您的项目添加一个名为 PlainTestDB 的 ADO.NET 实体数据模型。

  3. 从数据库中选择 EF 设计器。

  4. 将连接字符串添加到您新创建的数据库并选中复选框以将其保存到您的应用配置中。

  5. 选择实体框架 6.x

  6. 选中表格复选框并单击完成

  7. 将上面的代码复制并粘贴到您的 Form1.cs 中。确保使用粘贴的代码覆盖 button1_Click 方法。

  8. 添加以下 using 语句

  9. 使用 System.Threading;

  10. 使用 System.Diagnostics;

  11. 在线上放置一个断点

    Console.WriteLine("如果在调用方法中加载了实体,我永远不会被执行。");

  12. 在调试模式下运行程序并点击按钮

  13. 你会注意到计算器打开了,但是断点永远不会被命中

  14. 尝试一次注释掉一行,你会看到

  15. 注释掉数据库调用将解决挂起线程的问题

【问题讨论】:

  • 您是否尝试在调试器中单步执行?尝试创建一个minimal reproducible example。我相信你会在完成之前找到解决方案。
  • 移除无限循环while (true) { },让UI线程从点击处理程序返回。
  • @DavidBrowne-Microsoft 当然可以,但是我需要同时检查 Process.Start 何时完成,它在外部进程中运行。它并没有回答这个问题,为什么 Process.Start 和 Entity Framework 会互相阻塞。
  • @AndrewSavinykh 是的,我做到了。并且调试器只是没有转到 Process.Start 下面的行。注释掉实体框架代码行时除外。 “最小的可重现示例”是上面的示例。您所要做的就是对此进行测试:将实体框架查询添加到项目中,您将能够重现该问题,正如帖子中提到的那样。显然我不能将整个 SQL Server 和实体框架代码添加到“最小可重现示例”中,因为您需要有一个特定的表和数据库。
  • 可以粘贴到 VS、添加 EF 引用、指向 SQL 服务器并运行的东西。类似于this answer 中的代码块,它位于“这是一个独立的工作示例”之后。最少的代码应该创建它需要的数据库和表,就像在那个例子中一样。那将是一个“最小的可重现示例”。你的不是。

标签: c# entity-framework asynchronous


【解决方案1】:
    private void button1_Click(object sender, EventArgs e)
    {
        new Thread(BackgroundWorker).Start();
    }

    private void BackgroundWorker()
    {
        List<SimpleTestTable> list = new PlainTestDBEntities().SimpleTestTables.ToList();

        new Thread(StartProcess).Start();

        while (true)
        { 
            // Wait for the external Process to complete its work.
        }
    }

    private void StartProcess()
    {
        Thread.Sleep(2000);

        Console.WriteLine("I am executed totally fine.");

        Process.Start("calc.exe");

        Console.WriteLine("I am now always executed because there is no UI handler interfering with Entity Framework and Process.Start");
    }

这解决了问题。在控制台应用程序中尝试了第一个示例后,我发现它运行良好。因此,感谢一个友好的 cmet 中关于 UI 处理程序的提示,我认为 UI 处理程序必须首先完成它的工作才能使所有线程正常运行。

但它仍然没有回答为什么首先会出现这种现象。为什么在第一个示例中 Entity Framework、Click Handler 和 Process.Start 会相互阻塞?您可以轻松地进行测试,使用实体框架表设置一个简单的项目,如第一个示例中所述加载。只要您删除实体框架代码,while() 或 Process.Start 它就会起作用。但并不是所有的都在一起。

知道为什么会很有趣。

【讨论】:

  • Start.Process 使用 ShellExecute。 ShellExecutes 可以将执行委托给使用 COM 激活的 Shell 扩展。根据进程的单元模型,COM 消息传递可以依赖于进程消息泵。您正在阻止 while (true) 的消息泵。
  • 实体是一条红鲱鱼,我认为它与任何事情都没有任何关系。如果您可以提供一个可重现的示例,那么我们可以更详细地查看它,没有它就无法重现。
猜你喜欢
  • 2017-03-18
  • 2018-10-05
  • 2014-01-30
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-23
  • 1970-01-01
相关资源
最近更新 更多