WPF Task.Yield() UI issues spinning circle,Task.Delay(1) works fine,gives the UI message pump breathing room to repaint, handle input, and process your pending BeginInvoke calls.
private object objLock=new object(); private async Task LoadDataAsync() { var dir = @"../../../Images"; if (!Directory.Exists(dir)) { return; } await Task.Run(async () => { var imgs = Directory.GetFiles(dir); if (imgs!=null && imgs.Any()) { int imgsCount = imgs.Count(); List<Book> booksList = new List<Book>(); ItemsCollection=new ObservableCollection<Book>(); for (int i = 1; i<=1000000; i++) { booksList.Add(new Book() { Id=i, Author=$"Author_{i}", Name=$"Name_{i}", Chapter=$"Chapter_{i}", Summary=$"Summary_{i}", Title=$"Title+_{i}", Topic=$"Topic_{i}", Press=$"Press_{i}", ISBN=$"ISBN_{i}_{Guid.NewGuid().ToString("N")}", ImgSource=GetImgSourceViaImgUrl(imgs[i%imgsCount]) }); if(i%500==0) { List<Book> tempList; // safe copy on background thread lock (objLock) { tempList = booksList.ToList(); booksList.Clear(); } Application.Current?.Dispatcher.BeginInvoke(() => { foreach (var item in tempList) { ItemsCollection.Add(item); ItemsCount=ItemsCollection.Count; } }); } //await Task.Yield(); await Task.Delay(1); } } }); }
I did not catch the spinning circle snapshot

Generated by AI.
Why the difference?
-
Task.Yield()-
It does not sleep.
-
It just posts the continuation of your loop to the current
SynchronizationContext(or thread pool if none). -
In your case, the loop keeps hammering the dispatcher (
BeginInvoke) every iteration without real pause. -
The UI thread is still under constant load → it looks “busy” → Windows shows the spinning circle.
-
-
Task.Delay(1)-
Actually waits ~1ms (a real timer).
-
This gives the UI message pump breathing room to repaint, handle input, and process your pending
BeginInvokecalls. -
The system no longer thinks the UI thread is permanently blocked → no spinning circle.
-
Rule of thumb
-
Use
Task.Yield()when you just want to avoid stack overflows / very long synchronous continuations (i.e., "split the work"). -
Use
Task.Delay(...)when you need to give other things (like UI rendering) actual time to run. EvenTask.Delay(0)behaves differently fromTask.Yield()because it posts via a timer.
I had refined the best practice, This way, the UI can process input/rendering first, and only apply your items when idle via setting the Priority as System.Windows.Threading.DispatcherPriority.Background
private object objLock=new object(); private async Task LoadDataAsync() { var dir = @"../../../Images"; if (!Directory.Exists(dir)) { return; } await Task.Run(async () => { var imgs = Directory.GetFiles(dir); if (imgs!=null && imgs.Any()) { int imgsCount = imgs.Count(); List<Book> booksList = new List<Book>(); ItemsCollection=new ObservableCollection<Book>(); for (int i = 1; i<=1000000; i++) { booksList.Add(new Book() { Id=i, Author=$"Author_{i}", Name=$"Name_{i}", Chapter=$"Chapter_{i}", Summary=$"Summary_{i}", Title=$"Title+_{i}", Topic=$"Topic_{i}", Press=$"Press_{i}", ISBN=$"ISBN_{i}_{Guid.NewGuid().ToString("N")}", ImgSource=GetImgSourceViaImgUrl(imgs[i%imgsCount]) }); //Render ASAP if(i<=50 && i%10==0) { PopulateItemsCollection(booksList); // only once per batch, not every item await Task.Delay(1); } else if(i%2000==0) { PopulateItemsCollection(booksList); // only once per batch, not every item await Task.Delay(1); } } } }); } private void PopulateItemsCollection(List<Book> booksList) { List<Book> tempList; // safe copy on background thread lock (objLock) { tempList = booksList.ToList(); booksList.Clear(); } //DispatcherPriority.Background,this will Lower the dispatcher priority. //Post updates at DispatcherPriority.Background or ApplicationIdle instead of Normal. //This way, the UI can process input/rendering first, and only apply your items when idle. Application.Current?.Dispatcher.BeginInvoke(() => { ItemsCollection.AddRange(tempList); ItemsCount=ItemsCollection.Count; },System.Windows.Threading.DispatcherPriority.Background); }



//xaml <Window x:Class="WpfApp71.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" WindowState="Maximized" xmlns:local="clr-namespace:WpfApp71" mc:Ignorable="d" Title="{Binding ItemsCount,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="450" Width="800"> <Grid> <ListBox x:Name="lbx" ItemsSource="{Binding ItemsCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.ScrollUnit="Item" ScrollViewer.IsDeferredScrollingEnabled="True"> <ListBox.ItemContainerStyle> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="FontSize" Value="30"/> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="Height" Value="{Binding DataContext.LbxItemHeight, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"/> <Setter Property="Width" Value="{Binding DataContext.LbxItemWidth, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="FontSize" Value="50"/> <Setter Property="Foreground" Value="Red"/> </Trigger> </Style.Triggers> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate> <Grid Width="{Binding DataContext.LbxItemWidth, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}" Height="{Binding DataContext.LbxItemHeight, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"> <Grid.Background> <ImageBrush ImageSource="{Binding ImgSource}"/> </Grid.Background> <Grid.Resources> <Style TargetType="DataGridCell"> <Setter Property="Width" Value="Auto"/> </Style> <Style TargetType="{x:Type TextBlock}"> <Setter Property="VerticalAlignment" Value="Center"/> </Style> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Id}" Grid.Column="0"/> <TextBlock Text="{Binding Name}" Grid.Column="1"/> <TextBlock Text="{Binding Author}" Grid.Column="2"/> <TextBlock Text="{Binding Chapter}" Grid.Column="3"/> <TextBlock Text="{Binding Summary}" Grid.Column="4"/> <TextBlock Text="{Binding Title}" Grid.Column="5"/> <TextBlock Text="{Binding Topic}" Grid.Column="6"/> <TextBlock Text="{Binding Press}" Grid.Column="7"/> <TextBlock Text="{Binding ISBN}" Grid.Column="8"/> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window> //cs using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.ObjectModel; using System.IO; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApp71 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); RenderOptions.ProcessRenderMode=System.Windows.Interop.RenderMode.Default; var vm = new MainVM(this); this.DataContext=vm; } } public partial class MainVM : ObservableObject { private Window win; private ListBox lbx; public MainVM(Window winValue) { win= winValue; if (win!=null) { win.SizeChanged+=Win_SizeChanged; } //LoadDataAsyncByProgress(); LoadDataAsync(); } private void Win_SizeChanged(object sender, SizeChangedEventArgs e) { var tempLbx = win.FindName("lbx") as ListBox; if (tempLbx!=null) { lbx=tempLbx; LbxItemHeight=lbx.ActualHeight/2; LbxItemWidth=lbx.ActualWidth; } } private object objLock=new object(); private async Task LoadDataAsync() { var dir = @"../../../Images"; if (!Directory.Exists(dir)) { return; } await Task.Run(async () => { var imgs = Directory.GetFiles(dir); if (imgs!=null && imgs.Any()) { int imgsCount = imgs.Count(); List<Book> booksList = new List<Book>(); ItemsCollection=new ObservableCollection<Book>(); for (int i = 1; i<=1000000; i++) { booksList.Add(new Book() { Id=i, Author=$"Author_{i}", Name=$"Name_{i}", Chapter=$"Chapter_{i}", Summary=$"Summary_{i}", Title=$"Title+_{i}", Topic=$"Topic_{i}", Press=$"Press_{i}", ISBN=$"ISBN_{i}_{Guid.NewGuid().ToString("N")}", ImgSource=GetImgSourceViaImgUrl(imgs[i%imgsCount]) }); //Render ASAP if(i<=50 && i%10==0) { PopulateItemsCollection(booksList); // only once per batch, not every item await Task.Delay(1); } else if(i%2000==0) { PopulateItemsCollection(booksList); // only once per batch, not every item await Task.Delay(1); } } } }); } private void PopulateItemsCollection(List<Book> booksList) { List<Book> tempList; // safe copy on background thread lock (objLock) { tempList = booksList.ToList(); booksList.Clear(); } //DispatcherPriority.Background,this will Lower the dispatcher priority. //Post updates at DispatcherPriority.Background or ApplicationIdle instead of Normal. //This way, the UI can process input/rendering first, and only apply your items when idle. Application.Current?.Dispatcher.BeginInvoke(() => { ItemsCollection.AddRange(tempList); ItemsCount=ItemsCollection.Count; },System.Windows.Threading.DispatcherPriority.Background); } private ImageSource GetImgSourceViaImgUrl(string imgUrl) { BitmapImage bmi = new BitmapImage(); if (!File.Exists(imgUrl)) { return bmi; } bmi.BeginInit(); bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute); bmi.EndInit(); if (bmi.CanFreeze) { bmi.Freeze(); } return bmi; } [ObservableProperty] private ObservableCollection<Book> itemsCollection; [ObservableProperty] private int itemsCount; [ObservableProperty] private double lbxItemHeight; [ObservableProperty] private double lbxItemWidth; } public static class ObservableCollectionExtensions { public static void AddRange<Book>(this ObservableCollection<Book> collection, IEnumerable<Book> items) { foreach (var item in items) { collection.Add(item); } // Ideally, you would reflect and call OnCollectionChanged with a Reset action. // But a simple loop is clearer for this example. } } public class Book { public int Id { get; set; } public string Author { get; set; } public string Name { get; set; } public string Chapter { get; set; } public string Summary { get; set; } public string Title { get; set; } public string Topic { get; set; } public string Press { get; set; } public string ISBN { get; set; } public ImageSource ImgSource { get; set; } } }

浙公网安备 33010602011771号