winforms 如果在运行任务之前创建新表单示例,则等待任务挂起

2wnc66cl  于 2022-12-14  发布在  其他
关注(0)|答案(4)|浏览(142)

如果我在运行await代码之前运行create a new form示例,await代码将挂起。
如果我注解Form frm = new Form();行,代码将正常运行,否则它将挂起在代码await Task.Delay(2000);中。
另一个解决方案是使用Task.Run(示例代码中的注解行)创建一个新的表单示例。我不知道它为什么能工作,也不知道在子线程中创建一个新的表单示例是否合适。
这里有一个简单的示例代码来重现这个问题。有人知道为什么会发生这种情况吗?

using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ConsoleApp1
{
    class Program
    {
        public async static Task Main(string[] args)
        {
            //Form frm = await Task.Run( () => new Form());
            Form frm = new Form();
            await Delay();         
        }

        public static async Task Delay()
        {
            await Task.Delay(2000);
        }
    }
}

很抱歉造成了混乱。添加了我正在编写的真实的的代码,实际上是单元测试代码。

public async Task TestFrmLoginGen1()
    {
        IFrmLogin frmLogin;
        frmLogin = await Task.Run(() => new FrmLogin());
        //frmLogin = new FrmLogin();

        FrmLoginPresenter loginPresenter = new FrmLoginPresenter(frmLogin);
        await  loginPresenter.LoginAsync();
    }
g9icjywg

g9icjywg1#

其他回答说您不应该在控制台应用程序或单元测试中创建窗体,这是绝对正确的。
从更新后的代码来看,似乎有人已经不厌其烦地确保您可以对演示者进行单元测试,而不必示例化表单:FrmLoginPresenter构造函数接受一个IFrmLogin,我假设它是一个由FrmLogin实现的接口。
你要做的是创建一个IFrmLogin的模拟实现,这可能是你自己编写的一个实现IFrmLogin的普通类(它可能已经存在于你的测试套件中),或者它可能使用一个模拟库,比如Moq或Rhino Mocks。
然后将mock实现传递给FrmLoginPresenter构造函数。
最后,看起来无论是谁设计了你的应用程序,他都已经考虑过应该如何进行单元测试了。你可能应该去和他们谈谈,以了解他们的意图。

uwopmtnx

uwopmtnx2#

您的代码从未调用Application.Run()以启动应用程序的消息循环。
当你使用await时,根据上下文的不同,continuation可能会被发送到消息循环中。一旦你创建了一个表单,你就处于这样一个上下文中,你必须确保消息循环确实运行。
WinForms应用程序的初始化代码中通常调用Application.Run()的部分不适合使用async/await。

s4n0splo

s4n0splo3#

如果我在运行await代码之前运行create a new form示例,await代码将挂起。
这有很多原因。
1.您的应用程序实际上是 * 控制台应用程序 * 而不是WinForms 应用程序。您正在尝试在控制台应用程序中显示GUI,这通常是不可能的
1.控制台应用程序默认为MTA,而不是STASTA是运行GUI的所有应用程序必需
1.控制台应用程序不处理 *Windows消息泵 *,这与本机窗口或WinForms应用程序不同
当然,您可以添加一个消息泵到控制台应用程序;强制STA线程,但是当Visual Studio中有一个非常好的WinForms向导时,为什么还要强制STA线程呢?
我注解了行Form frm = new Form();代码将正确运行
我真的很怀疑。你可能会有一个窗口 * 出现 *,但它肯定不会是 * 交互式 *。除非消息泵被处理,否则窗口不会绘制(包括在它前面的窗口被移走后绘制)。单击按钮和菜单无响应计时器无效
最后,不要试图通过线程和/或Task创建GUI,因为您对STA几乎没有控制权。这一切都必须通过应用程序的主线程来完成,主线程必须是前面提到的STA。没有理由使用多个STA
总之,在构造窗口时使用async/await没有什么意义。

f5emj3cl

f5emj3cl4#

我有点晚了,但我发现自己遇到了同样的问题(尽管我的用例是一个可以作为无窗口进程或GUI运行的应用程序)。
执行HTTP异步操作似乎是一个大问题(Task.Delay没有导致死锁/挂起)。

static async Task Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    var fooForm = new Form1();
    fooForm.RunLoginTest().Wait();
    Application.Run(new Form1());
}
public async Task RunLoginTest() {
    var tokenClient = new TokenClient("https://dev1idsrv.biz/connect/" + "token", "----", "----");
    var tokenResponse = await tokenClient.RequestClientCredentialsAsync("cloud_api_gateway");
}

正如其他人所说,在Application.Run之前运行异步代码会导致问题。下一次尝试是通过将Application.Run()粘贴在构造函数方法中来触发它内部的异步代码。

public Form1()
{
    InitializeComponent();
    RunLoginTest().Wait();
}

正如您所预料的那样,上面的方法也失败了,显然表单实际上必须在Application.Run发挥作用之前创建。
因此,将代码粘贴在form_load事件中是最后起作用的

private async void MyForm_Load(object sender, EventArgs e)
{
    this.Opacity = 0; //to hide the form
    this.ShowInTaskbar = false; //to hide the form
    await MyAsyncMethod();                
    Application.Exit();//or Form.Close() or neither, depending on your use case
}

对于我的用例,我希望代码运行但不显示窗体。任何设置.visible的尝试都会被覆盖,因此我通过其他方法将其设置为不可见。上面的代码块成功地运行了异步代码,而没有触发死锁

相关问题