英文:
What is the proper way to call an async method containing a loop without blocking the UI?
问题
以下是您提供的代码的翻译部分:
当运行以下代码时,会阻止UI更新。
ProcessDocumentRange方法的作用是循环遍历一个范围,并调用BuildDocumentList方法,为循环的每次迭代添加一个文档引用到List<Document>中。
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()
{
// 调用会冻结UI的方法
await ProcessDocumentRange(_startId, _dest);
}
public async Task ProcessDocumentRange(int _startID, string _root)
{
int year = 28;
for (int i = _startID; i < _endId; i++) // 冻结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));
任何帮助将不胜感激。
更新代码,现在引发异常:"调用线程必须是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)
{
// 在下面的await Task.Run中引发异常。
// 异常可能与将项目添加到列表有关
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类
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;
}
}
}
请注意,这只是提供给您的代码的翻译部分,不包括问题的回答或其他内容。如果您需要进一步的帮助或解释,请告诉我。
英文:
I have the below code when run blocks the UI from updating.
The ProcessDocumentRange method’s job is to loop through a range and call the BuildDocumentList method to add a document reference to List<Document> for each iteration of the loop.
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 });
}
I have tried calling the Method like the following and the UI doesn't freeze, but the loop exits and never builds the list: await Task.Run(() => ProcessDocumentRange(_startId, _dest));
Any help would b greatly appreciated.
Update to code that now throws Exception: "The calling thread must be STA, because many UI components require this."
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;
}
}
}
答案1
得分: 1
以下是您要翻译的代码部分:
Your methods are executing synchronously because none of them returns an actual `Task`. But most importantly, you failed to reach your goal because you did not off-load the CPU intensive work to relieve the UI thread. It's the loop that still exhausts the UI thread and therefore freezes the UI.
To move it to a different thread, a background thread, you must call `Task.Run`:
private async void _btnStartClick(object sender, EventArgs e)
{
// 调用不再冻结UI的方法。
// 使用await异步等待方法返回
await ProcessDocumentRangeAsync(_startId, _dest);
// TODO::对已填充的列表执行某些操作
}
public async Task ProcessDocumentRangeAsync(int startID, string root)
{
// 在后台线程上执行循环
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))
{
return;
}
// 因为Document扩展自MainWindow,
// 我们必须在原始的Dispatcher线程(UI线程)上创建实例。
// 这是因为MainWindow继承自Window的DispatcherObject。
// DispatcherObject实例具有线程亲和性(Dispatcher亲和性)。
// 在不是关联的Dispatcher线程上创建它们或从它们访问它们
// 会引发跨线程异常(InvalidOperationException)。
// 为了解决这个问题,我们必须使用Dispatcher在Dispatcher线程上执行实例化。
// 我们使用DispatcherPriority.Background来防止循环耗尽UI线程。
// 如果我们可以摆脱Dispatcher.Invoke,我们可以提高性能,
// 比如避免将Document设置为窗口。
// 而是应该是一个简单的数据模型(而不是控件)。
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);
}
如果您需要进一步的翻译或有其他问题,请告诉我。
英文:
Your methods are executing synchronously because non of them returns an actual Task
. But most important, you failed to reach your goal because you did not off-load the CPU intensive work to relief the UI thread. It's the loop that still exhausts the UI thread and therefore freezes the UI.
To move it to a different thread, a background thread, you must call 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);
}
If you don't have anything to do after the list is populated, you don't have to use await
. Simply create the background thread and return to the UI thread and continue:
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);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论