1

My await code hangs if I run a create a new form instance before running the await code.

If I comment the line Form frm = new Form(); the code will be run properly otherwise it will hang in the code await Task.Delay(2000);.

Another solution is to create a new form instance using Task.Run(the commented line in my example code). I have no idea why it does work and doesn't know if that's fine to create a new form instance in a subthread.

Here is a simple example code to replicate the problem. Is there anyone have the idea why does this happen?

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);
        }
    }
}

Sorry for the confusion. Added the real code I am writing which is actually the unit test code.

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

        FrmLoginPresenter loginPresenter = new FrmLoginPresenter(frmLogin);
        await  loginPresenter.LoginAsync();
    }
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Lily Liu
  • 35
  • 5
  • 4
    I don't see how this could work either way - you're not calling `Application.Run`, so you're not starting the winforms message loop. – canton7 Mar 01 '19 at 11:31
  • 3
    This code doesn't exhibit any deadlock. It shows an attempt to modify the UI from a background thread, something that's explicitly forbidden - assuming the *form* run at all. As `canton7` that code isn't enoug to start the desktop application – Panagiotis Kanavos Mar 01 '19 at 11:32
  • 1
    `if that's fine to create a new form instance in a subthread.` why? Creating a new form isn't expensive, or at least it *shouldn't* be. Loading data the first time can be slow, and that can be done in the background. There's no reason to create the form in the background though. – Panagiotis Kanavos Mar 01 '19 at 11:34
  • 1
    This looks like an XY problem and I suspect the *actual* problem is slow data/form loading. What does the form do that takes so long? Why do that in the *constructor*, instead of a more natural place like eg the Load event? – Panagiotis Kanavos Mar 01 '19 at 11:35
  • @Panagiotis Kanavos sorry I didn't post my real situation but just post the code to replicate the issue. I am actually writing the unit test code and need to run some public functions in the form classes which are async functions. But the test code hangs when calling the await task. I created these simple code to try to isolate the issue. – Lily Liu Mar 01 '19 at 11:40
  • I think your mistake is unit-testing code in forms. I suspect the form has taken control of the thread, and isn't releasing it to run the continuation from the `Task.Delay` (although the debugger will prove/disprove this). Move that code out to an independent service. – canton7 Mar 01 '19 at 11:41
  • @LilyLiu post the test code then. The code here doesn't even work. Instead of demonstrating the problem it shows some far more serious problems – Panagiotis Kanavos Mar 01 '19 at 11:42
  • I think the bigger question is **why are you creating a GUI inside a console app?**. If you want a GUI create a WPF or WinForms app. Don't try and munge a console app to do what it was not designed to do. Console apps don't default to `STA` either –  Mar 01 '19 at 11:54
  • @Panagiotis Kanavos Thanks. I have edited the question and added the real code to the original question. – Lily Liu Mar 01 '19 at 11:55
  • @MickyD Sorry. my mistake. It is not my real code. It is to demostrate the problem. I've edited my question. – Lily Liu Mar 01 '19 at 11:56
  • @canton7 Yeah. I tried to code out to an independent service but still as long as there is a creating a new form instance run first then await Task must hang. I think Wim is right. – Lily Liu Mar 01 '19 at 12:01
  • @canton7 Sorry. somehow I missed your reply: "I don't see how this could work either way - you're not calling Application.Run, so you're not starting the winforms message loop" - Yeah. I think that's the cause of the problem. – Lily Liu Mar 01 '19 at 12:09
  • 1
    @LilyLiu If the answers have helped resolve your issue, please consider upvoting them and accepting one. If you're still stuck, please add more detail. – canton7 Mar 01 '19 at 13:07

4 Answers4

2

The other answers saying that you shouldn't be creating forms inside a console app or a unit test are absolutely correct.

From your updated code, it looks like someone has already gone to the trouble of making sure you can unit-test your presenter without having to instantiate your form: the FrmLoginPresenter constructor takes an IFrmLogin, which I'm assuming is an interface implemented by FrmLogin. This abstraction exists to allow you to unit-test FrmLoginPresenter without having to create an actual FrmLogin.

What you want to do is to create a mock implementation of IFrmLogin. This might be a normal class which you write yourself which implements IFrmLogin (it might already exist in your test suite), or it might use a mocking library like Moq or Rhino Mocks.

Then you pass your mock implementation to the FrmLoginPresenter constructor.

Ultimately, it looks like whoever architected your application has already thought about how it should be unit-tested. You should probably go and speak to them to get the full picture on their intentions.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • 1
    Thanks, @canton7. You are right. Actually, I was the one architected my application. I am new and just started to create the form application. I started my application with only FrmLogin without Interface and presenter until I was struck by unit testing. Then I realized that I might be totally wrong in the architect with unit test. So I did some research on how do some other people do, then changed my architect to what you've seen but did not realize I should use mock. Thanks again. Honestly, before your post, I was still not confident that my new architect is right to continue. – Lily Liu Mar 02 '19 at 18:43
  • 1
    @Lily My bad. I assumed it wasn't you, as the only reason to hide the form behind an interface would be to allow mocking it for testing the presenter, but that's the bit you were having trouble with! – canton7 Mar 02 '19 at 19:18
  • No. It was my fault to not post my real problem in the first place. This was the first time I posted question on stack Overflow, still learning how to do it better. Also, "the only reason to hide the form behind an interface would be to allow mocking it for testing the presenter" - Thanks for pointing out this which is another question that I am trying to find the answer from google. Thanks again :) – Lily Liu Mar 03 '19 at 20:17
1

Your code is never calling Application.Run() to start the message loop of the application.

When you use await, the continuation may be posted to the message loop depending on the context. Once you create a form, you are in such a context and you have to make sure that the message loop actually runs.

The bit of initialization code of a WinForms application where Application.Run() is normally called is not a good place to use async/await.

Wim Coenen
  • 66,094
  • 13
  • 157
  • 251
  • Thanks Wim. It sounds make sense. I am actually writing the unit test for some windows form applications and don't want to bring the form up. Sorry I am new of c# and applications development. I don't if this is a good idea to write the unit test case for c# form application. – Lily Liu Mar 01 '19 at 11:51
  • @LilyLiu This is a commonly encountered issue when creating automated tests. I believe the best solution is to split your forms into viewmodels (which can be regular classes) and thin views (i.e. forms) that contain no logic but just expose the information and actions that are defined in the viewmodels. In the automated tests, you can then omit the views. I've followed this approach when I wrote [Downmarker](https://bitbucket.org/wcoenen/downmarker). – Wim Coenen Mar 01 '19 at 13:03
  • It looks like that's what the author of the code did - except using MVP, not MVVM. The presenters take interfaces to the forms. – canton7 Mar 01 '19 at 13:04
0

My await code hangs if I run a create a new form instance before running the await code.

Well there are a number of reasons for that.

  1. Your app is actually a console app and not a WinForms app. You are attempting to show a GUI in a console app which typically isn't possible

  2. Console apps default to a MTA rather than STA. STA is required for all apps running a GUI

  3. Console apps don't process the Windows Message Pump unlike say native windows or a WinForms app

Sure you can add a message pump to a console app; force a STA thread but why would you when there is a perfectly good WinForms wizard in Visual Studio?

I comment the line Form frm = new Form(); the code will be run properly

I really doubt that. You might have a window appear but it certainly won't be interactive. Unless the message pump is processed the window won't paint (including painting after a window, that was in front of it, is moved away). Clicking buttons and menus will be unresponsive. Timers will be inoperative.

Finally, don't attempt to create a GUI via a thread and/or Task as you will have little control over STA. It all must be done from the application's primary thread which must be STA as mentioned. There is little reason for multiple STAs.

In conclusion there is little point for using async/await during construction of a window.

  • "Sure you can add a message pump to a console app; force a STA thread but why would you when there is a perfectly good WinForms wizard in Visual Studio?" It's just a couple of lines of code. You can write them yourself easily enough. You don't need a fancy editor to do it for you. – Servy Mar 01 '19 at 22:39
  • True, but it generally prevents you from starting an app off wrong - the OP's question a case in point –  Mar 02 '19 at 00:36
0

I'm a bit late to the party, but I found myself running into the same problem (Although my use case was an app that can be run as a windowless process or a GUI). I'm going to leave my solution for the next poor soul who wanders here.

Doing HTTP Async operations seem to be a big culprit (Task.Delay didn't cause deadlocks/hangs for me). The following triggered the same behaviour as OP

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");
}

As stated by others, running async code before Application.Run causes issues. The next attempt was triggering the async code inside the Application.Run() by sticking it inside the constructor method.

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

As you'd expect the above also failed, evidently the form actually has to be created before Application.Run does its magic.

Accordingly, sticking the code in the form_load event was what ended up working

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
}

For my use case I wanted the code to run but not have the form show up. Any attempt to set .visible gets overriden, so instead I've set it invisible through other methods. The above code block successfully runs the async code without triggering deadlocks

The Lemon
  • 1,211
  • 15
  • 26
  • 1
    Yeah, the only problem is that every time you show the form again it will run form load method again. Not sure you want to necessarily tie the 2. – Tanuki Feb 06 '23 at 12:55
  • 1
    I guess that depends on your use case. You could always add a bool _init = false; flag or something to force the code to only ever trigger once. Alternatively you could use 'Opacity' and 'ShowInTaskbar' instead of form.show and .hide to control the form's existance (which you might actually need if you're using hacky enough code to send you to this page lol) – The Lemon Feb 07 '23 at 23:25