1

I'm taking a Udemy class and they are trying to explain threading. The full error I am receiving is

Warning CS1998 This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

I have reviewed many of the current SO answers and they don't quite match my situation at least to the level of my current understanding.

The learning project is a simple WPF with 2 buttons that when clicked download files and a counter for the current time. The goal is to ensure that a user can interact with the UI while downloading the files. The counter shows that the UI is updating.

The instructor states

"So here we created a asynchronous task which does that in the background, and once that is done, we can go ahead with the rest. So it's not going to freeze our main thread, our UI is still going to work properly..."

He includes the async keyword in the body of both button event handlers even though the compiler warns that it will run synchronously which does not appear to be true. The code runs fine if the async keyword is removed before the lambda expression. Both methods contain this code:

await Task.Run(async () => {...});

Code follows.

MainWindow.xaml.cs

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

using System.Windows.Threading;

namespace ThreadTasks_WPF_question
{

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(1);
            timer.Tick += timer_Tick;
            timer.Start();
        }
        void timer_Tick(object sender, EventArgs e)
        {
            MyLabel1.Content = DateTime.Now.ToLongTimeString();
        }
        private async void MyButton1_Click(object sender, RoutedEventArgs e)
        {
            
            MyButton1.Content = "MyButton1 Downloading";
            Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} before await button 1");
            
             // async below causes warning
            await Task.Run(async () =>
            {

                Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} during button 1");
                HttpClient webClient2 = new HttpClient();
                string userAgent2 = @"Mozilla/5.0 (compatible; word-word-name.name@domain.com)";
                webClient2.DefaultRequestHeaders.Add("User-Agent", userAgent2);
                string html = webClient2.GetStringAsync("http://ipv4.download.thinkbroadband.com/20MB.zip").Result;


            });

            Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} after await button 1");
            MyButton1.Content = "MyButton1 Done";
        }
        private async void MyButton2_Click(object sender, RoutedEventArgs e)
        {
            MyButton2.Content = "MyButton2 Downloading";
            Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} before await button 2");
          
            // async below causes warning
            await Task.Run(async () =>
            {
                Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} during button 2");
                HttpClient webClient = new HttpClient();
                string userAgent2 = @"Mozilla/5.0 (compatible; word-word-name.name@domain.com)";
                webClient.DefaultRequestHeaders.Add("User-Agent", userAgent2);
                string html = webClient.GetStringAsync("http://ipv4.download.thinkbroadband.com/20MB.zip").Result;
            });

            Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} after await button 2");
            MyButton2.Content = "MyButton2 Done";
        }
    }
}

MainWindow.xaml

<Window x:Class="ThreadTasks_WPF_question.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ThreadTasks_WPF_question"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Button Name="MyButton1" HorizontalAlignment="Left" Margin="275,114,0,0" VerticalAlignment="Top" Height="70" Width="210" Click="MyButton1_Click">
            <Button Content="Button 1"/>
        </Button>
        <Button Name="MyButton2" HorizontalAlignment="Left" Margin="275,234,0,0" VerticalAlignment="Top" Height="70" Width="210" Click="MyButton2_Click">
            <Button Content="Button 2"/>
        </Button>
        <Label Name="MyLabel1" Content="Label" HorizontalAlignment="Left" Margin="360,344,0,0" VerticalAlignment="Top"/>

    </Grid>
</Window>```

Was this an error by the instructor? The button click methods already had an async modifier. I'm not understanding what was intended and I would prefer not to learn bad habits or patterns.

2 Answers2

4

The problem is this line:

string html = webClient2.GetStringAsync("http://ipv4.download.thinkbroadband.com/20MB.zip").Result;

It should be:

string html = await webClient2.GetStringAsync("http://ipv4.download.thinkbroadband.com/20MB.zip");

But really you shouldn't be using Task.Run at all here. There's no reason to do so. Your code can be simply written like this:

private async void MyButton1_Click(object sender, RoutedEventArgs e)
{
    
    MyButton1.Content = "MyButton1 Downloading";
    Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} before await button 1");
    
    Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} during button 1");
    HttpClient webClient2 = new HttpClient();
    string userAgent2 = @"Mozilla/5.0 (compatible; word-word-name.name@domain.com)";
    webClient2.DefaultRequestHeaders.Add("User-Agent", userAgent2);
    string html = await webClient2.GetStringAsync("http://ipv4.download.thinkbroadband.com/20MB.zip");


    Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} after await button 1");
    MyButton1.Content = "MyButton1 Done";
}

Note that Microsoft's recommendation is that HttpClient be a static field or property within your application, not recreated each time you use it. For others reading: if you're making an ASP.NET Core application, you should use HttpClientFactory.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • Actually there might be good reasons to wrap asynchronous calls inside a `Task.Run`. I've posted my arguments [here](https://stackoverflow.com/questions/38739403/await-task-run-vs-await/58306020#58306020 "await Task.Run vs await"). – Theodor Zoulias May 22 '23 at 12:05
1

Either remove the async keyword from the delegate that you pass to Task.Run , i.e. change from await Task.Run(async () => { ... } to await Task.Run(() => { ... }.

Or better yet, await the GetStringAsync method:

await Task.Run(async () =>
{
    Debug.WriteLine($"Thread No. {Thread.CurrentThread.ManagedThreadId} during button 1");
    HttpClient webClient2 = new HttpClient();
    string userAgent2 = @"Mozilla/5.0 (compatible; word-word-name.name@domain.com)";
    webClient2.DefaultRequestHeaders.Add("User-Agent", userAgent2);
    string html = async webClient2.GetStringAsync("http://ipv4.download.thinkbroadband.com/20MB.zip");
});
mm8
  • 163,881
  • 10
  • 57
  • 88