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

image

 

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 BeginInvoke calls.

    • 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. Even Task.Delay(0) behaves differently from Task.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);
}

 

 

 

 

 

 

image

 

 

 

 

 

 

 

 

 

 

 

 

 

 

image

 

 

 

image

 

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

 

posted @ 2025-09-06 13:10  FredGrit  阅读(16)  评论(0)    收藏  举报