简体   繁体   中英

Releases UI thread while long method is running using await and async

I'm starting to explore async and wait keywords from c# 5.0 so i made a few test using winforms, but now i'm stuck in a situation:

My point is to run a query from a database and while the query is not completed i want to show a loading gif on form.

This is the method that queries the database:

public static async Task<List<string>> GetItensFromDatabase()
    {
        List<string> names = new List<string>();

        using (ServerConn)
        {
            ServerConn.Open();
            SqlCommand cmd = new SqlCommand("Select * From Names", ServerConn);

            var ds = new DataSet();
            var adapter = new SqlDataAdapter(cmd);
            adapter.Fill(ds); // this call lasts about 1 minute

            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                names.Add(dr["Name"].ToString());
            }
        }
        return names;
    }

And i call that method here:

private async void button1_Click(object sender, EventArgs e)
    {
        List<string> itens = null;
        itens = await AsyncMethods.GetItensFromDatabase(); // I want that the form dont be stuck here

        ShowItensInListView(itens);
    }

For now i have a omnipresent loading image gif on the form that is rotating until the method GetItensFromDatabase be called, when the method is running the gif is stop and when the method finishes the gif start rotating again.

So, theres some way to keep the gif rotating while the GetItensFromDatabase method is running ?

You could have used the await keyword inside your async method, but since there is no awaitable operation, you should create a Task explicitly inside your method using the TaskFactory :

public static Task<List<string>> GetItensFromDatabase()
{
    return Task.Factory.StartNew<List<string>>(() => 
    { 
        List<string> names = new List<string>();

        using (ServerConn)
        {
            ServerConn.Open();
            SqlCommand cmd = new SqlCommand("Select * From Names", ServerConn);

            var ds = new DataSet();
            var adapter = new SqlDataAdapter(cmd);
            adapter.Fill(ds); // this call lasts about 1 minute

            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                names.Add(dr["Name"].ToString());
            }
        }
        return names;
    });
}

Edit: As @mmarques points out, another (better) solution is to use SqlDataReader instead of SqlDataAdapter . The reason is that SqlDataReader has async methods available - meaning you don't need to use TaskFactory to block a new thread from the thread pool, and you can use async and await .

public static async Task<List<string>> GetItensFromDatabase()
{
    List<string> names = new List<string>();

    using (ServerConn)
    {
        using (SqlCommand cmd = new SqlCommand("Select * From Names", ServerConn))
        {
            ServerConn.Open();

            SqlDataReader reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection);
            if (reader.HasRows)
            {
                DataTable dt = new DataTable();
                dt.Load(reader);

                foreach (DataRow dr in dt.Rows)
                {
                    names.Add(dr["Name"].ToString());
                }
            }
        }
    }

    return names;
}

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