简体   繁体   中英

async/await running on single thread

I have a console application where in some instances a user interface needs to be presented. This user interface needs to remain responsive as it will contain a loading gif, progress bar, cancel button etc. I have the following sample code:

class Program
{
    static void Main(string[] args)
    {
        DoWork().GetAwaiter().GetResult();
    }

    private static async Task DoWork()
    {
        TestForm form = new TestForm();
        form.Show();

        string s = await Task.Run(() =>
        {
            System.Threading.Thread.Sleep(5000);
            return "Plop";
        });

        if (s == "Plop")
        {
            form.Close();
        }
    }
}

I would expect from the code above for the TestForm to be displayed for approximately 5 seconds before being closed due to the value of the string being "Plop", however all that happens is the Task is run and the if statement is never reached. Furthermore the UI of the TestForm does not remain responsive. What is wrong with this code?

Its a classic deadlock.When your code hit await ,control goes back to main thread which is a blocking wait for DoWork GetResult(); When Task.Run thread is finished controls tries to go back to main thread but its waiting for DoWork to be finished. That is the reason last If statement never executes.

But apart from deadlock ,there is also one more issue in your code which will make your UI freeze.Its the form.Show() method.If you remove everything related to async-await and only use form ,it will still freeze.The problem is Show method expects a windows message loop which will be provided if you create a Windows.Forms application but here you are launching form from console application which doesnt have a message loop. One solution would be to use form.ShowDialog which will create its own message loop. Another solution is to use System.Windows.Forms.Application.Run method which provides a win messages loop to the form created through thread pool thread. I can give you one possible solution here but its up to you how you structure your code as the root cause is identified.

static void Main(string[] args)
    {
        TestForm form = new TestForm();
        form.Load += Form_Load;

        Application.Run(form);

    }

    private static async void Form_Load(object sender, EventArgs e)
    {
        var form = sender as Form;
        string s = await Task.Run(() =>
        {
            System.Threading.Thread.Sleep(5000);
            return "Plop";
        });

        if (s == "Plop")
        {
            form?.Close();
        }
    }

So I've managed to hack together a dirty solution for this. It is not a clean solution so I'm still open to suggestions but for what I need it works fine

private static void DoWork()
    {
        TestForm form = new TestForm();

        Task formTask = Task.Run(() => form.ShowDialog());

        Task<string> testTask = Task.Run(() =>
        {
            for (int i = 1; i < 10; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine(i.ToString());
            }

            Console.WriteLine("Background task finished");
            return "Plop";
        });
        Console.WriteLine("Waiting for background task");

        testTask.Wait();

        if (testTask.Result == "Plop")
        {
            Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
        }

        Console.WriteLine("App finished");
    }

This outputs 'Waiting for background task' first, followed by the number count of the Task and then outputs 'Background task finished' when the long process is complete, as well as closes the responsive UI form

Ok I did mark my first answer to be deleted, since what I put there works for WPF and not for you require, BUT in this one is doing what you asked, I did try it and opens the WinForm then closes after 5 seconds, here is the code:

static void Main(string[] args)
{
    MethodToRun();
}

private static async void MethodToRun()
{
    var windowToOpen = new TestForm();
    var stringValue = String.Empty;
    Task.Run(new Action(() =>
    {
        Dispatcher.CurrentDispatcher.InvokeAsync(() =>
        {
            windowToOpen.Show();
        }).Wait();
        System.Threading.Thread.Sleep(5000);
        stringValue = "Plop";
        Dispatcher.CurrentDispatcher.InvokeAsync(() =>
        {
            if (String.Equals(stringValue, "Plop"))
            {
                windowToOpen.Close();
            }
        }).Wait();
    })).Wait();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM