wpf 调用包含循环的异步方法而不阻塞UI的正确方法是什么?

qmelpv7a  于 2023-05-19  发布在  其他
关注(0)|答案(1)|浏览(209)

我有下面的代码时运行块更新的UI。
ProcessDocumentRange方法的工作是在一个范围内循环,并调用BuildDocumentList方法,以便为循环的每次迭代添加对List的文档引用。

public List<Document> _queuedDocuments = new List<Document>();
 public string _instID = "";        
 static String _dest = "c:\\test";
 static Int32 _startId = 50001;
 static Int32 _endId = 65001;
private async void _btnStartClick(object sender, EventArgs e)
{
    await ProcessRecords();
}
private async Task ProcessRecords()
{
    // Call to Method which freezes UI
    await ProcessDocumentRange(_startId, _dest);
}
public async Task ProcessDocumentRange(int _startID, string _root)
{
    int year = 28;
    for (int i = _startID; i < _endId; i++) //Start of loop that freezes UI
    {
        _instID = year.ToString() + i.ToString().PadLeft(5, '0');
        await BuildDocumentList(year, i, _instID, _root);
    }
}
private async Task BuildDocumentList(int year, int image, string instID, string root)
{
    string documentIndexFile = root + year.ToString() + "\\" + instID + ".txt";

    if (!File.Exists(documentIndexFile))
        _queuedDocuments.Add(new Document { Year = year, Image = image });
}

我试着像下面这样调用方法,UI没有冻结,但是循环退出,并且永远不会构建列表:await Task.Run(() => ProcessDocumentRange(_startId, _dest));
任何帮助都将不胜感激。
更新现在抛出Exception的代码:调用线程必须是STA,因为许多UI组件都要求此线程。

public partial class MainWindow : Window
    {
        public List<Document> _queuedDocuments = new List<Document>();
public MainWindow()
        {
            InitializeComponent();
            _tbURL.Text = _url;
            _tbDest.Text = _dest;
            _tbStartId.Text = _startId.ToString();
            _tbEndID.Text = _endId.ToString();
        }
private async void _btnStartClick(object sender, EventArgs e)
        {
            await ProcessDocumentRange(_startId, _dest);
        }
public async Task ProcessDocumentRange(int _startID, string _root)
        {
            //Exception thrown at await Task.Run below.
            //Exception may have something to do with adding an item to the list
            await Task.Run(() =>
            {
                int year = 28;
                for (int i = _startID; i < _endId; i++)
                {
                    _instID = year.ToString() + i.ToString().PadLeft(5, '0');
                    BuildDocumentList(year, i, _instID, _root);

                }
            });
        }
private void BuildDocumentList(int year, int image, string instID, string root)
        {
            string documentIndexFile = root + year.ToString() + "\\" + instID + ".txt";

            if (!File.Exists(documentIndexFile))
                _queuedDocuments.Add(new Document { Year = year, Image = image });
        }
//Document Class
    public class Document : MainWindow
    {        
        private int _year = -1;
        public int Year
        {
            get
            {
                return _year;
            }
            set
            {
                _year = value;
            }
        }

        private int _image = -1;
        public int Image
        {
            get
            {
                return _image;
            }
            set
            {
                _image = value;
            }
        }
   }
3z6pesqy

3z6pesqy1#

您的方法是同步执行的,因为它们都没有返回实际的Task。但最重要的是,您未能达到目标,因为您没有卸载CPU密集型工作以减轻UI线程的负担。正是这个循环仍然耗尽UI线程,因此冻结了UI。
要将其移动到不同的线程(后台线程),必须调用Task.Run

private async void _btnStartClick(object sender, EventArgs e)
{
  // Call to Method which no longer freezes the UI.
  // Use await to wait asynchronously for the method to return
  await ProcessDocumentRangeAsync(_startId, _dest);

  // TODO::Do something with the populated list
}

public async Task ProcessDocumentRangeAsync(int startID, string root)
{
  // Execute loop on a background thread
  await Task.Run(() =>
  {
    int year = 28;
    for (int i = startID; i < _endId; i++)
    {
      _instID = year.ToString() + i.ToString().PadLeft(5, '0');
      BuildDocumentList(year, i, _instID, root);
    }
  });
}

private BuildDocumentList(int year, int image, string instID, string root)
{
  string documentIndexFile = root + year.ToString() + "\\" + instID + ".txt";
  if (File.Exists(documentIndexFile))
  {
    return;
  }

  // Because Document extends MainWindow, 
  // we must create the instance on the original Dispatcher thread (UI thread).
  // This is required because MainWindow inherits the DispatcherObject from Window.  
  // DispatcherObject instances have thread affinity (Dispatcher affinity).
  // Creating them on or accessing them from a thread that is not the associated Dispatcher thread
  // will throw a cross-thread exception (InvalidOperationException).
  // To solve this, we must execute the instantiation on the Dispatcher thread using the Dispatcher.
  // We use DispatcherPriority.Background to prevent the loop from exhausting the UI thread.
  // We could improve the performance if we could get rid of the Dispatcher.Invoke,
  // for example by avoiding making Document a Window. 
  // Instead it should be a simple data model (and not a control).
  this.Dispatcher.InvokeAsync(
    () => CreateDocument(year, image), 
    DispatcherPriority.Background);
}

private void CreateDocument(int year, int image)
{
  var newDocument = new Document { Year = year, Image = image };
  _queuedDocuments.Add(newDocument);
}

如果在列表填充后没有任何事情要做,则不必使用await。只需创建后台线程并返回到UI线程并继续:

private async void _btnStartClick(object sender, EventArgs e)
{
  // Call to Method which no longer freezes the UI.
  // Because of we don't await anything here 
  // the method is a pure concurrent method and we can 
  // immediately continue to do work on the current thread.
  ProcessDocumentRange(_startId, _dest);

  // TODO::The list is not populated at this point. 
  // The operation is still executing in parallel-
}

public void ProcessDocumentRange(int startID, string root)
{
  // Execute loop concurrently on a background thread 
  // and return immediately to the caller (click handler) 
  Task.Run(() =>
  {
    int year = 28;
    for (int i = startID; i < _endId; i++)
    {
      _instID = year.ToString() + i.ToString().PadLeft(5, '0');
      BuildDocumentList(year, i, _instID, root);
    }
  });
}

private BuildDocumentList(int year, int image, string instID, string root)
{
  string documentIndexFile = root + year.ToString() + "\\" + instID + ".txt";
  if (File.Exists(documentIndexFile))
  {
    return;
  }

  // Because Document extends MainWindow, 
  // we must create the instance on the original Dispatcher thread (UI thread).
  // This is required because MainWindow inherits the DispatcherObject from Window.  
  // DispatcherObject instances have thread affinity (Dispatcher affinity).
  // Creating them on or accessing them from a thread that is not the associated Dispatcher thread
  // will throw a cross-thread exception (InvalidOperationException).
  // To solve this, we must execute the instantiation on the Dispatcher thread using the Dispatcher.
  // We use DispatcherPriority.Background to prevent the loop from exhausting the UI thread.
  // We could improve the performance if we could get rid of the Dispatcher.Invoke,
  // for example by avoiding making Document a Window. 
  // Instead it should be a simple data model (and not a control).
  this.Dispatcher.InvokeAsync(
    () => CreateDocument(year, image), 
    DispatcherPriority.Background);
}

private void CreateDocument(int year, int image)
{
  var newDocument = new Document { Year = year, Image = image };
  _queuedDocuments.Add(newDocument);
}

相关问题