Reputation: 474
I am trying to add threading into my program such that I don't freeze the entire main UI thread while doing "expensive" computational work.
Currently, my program runs an async Task
pointing to a function named startWork()
when a button is pressed like so:
async void startParse_Click(object sender, EventArgs e)
{
await Task.Run(() => startWork());
}
Normally for setting values I do the following:
niceButton.BeginInvoke(new MethodInvoker(() =>
{
niceButton.Text = "new text";
}));
However, for grabbing data from controls and using that data outside of the MethodInvoker
, I'm having a bit of trouble. My goal is execute a foreach
loop around my listView1.Items
, outside of the UI thread.
Here are the contents of startWork()
:
void startWork()
{
// Naturally I cannot reference my listView1 control because it is in a
// different thread and is blocked the the "illegal" cross-thread check
int overallProgress = 0;
ListView.ListViewItemCollection items = null;
// This unfortunately doesn't work (MethodInvoker is in a different scope?)
listView1.BeginInvoke( new MethodInvoker(() => {
items = listView1.Items;
}));
int totalItems = items.Count; // "items" isn't recognized
foreach (ListViewItem files in items )
{
// slowwww work
}
}
I have also tried passing the ListView.ListViewItemCollection
as an argument to the function with no avail.
Continuing to get Cross-thread operation not valid: accessed from a thread other than the thread it was created on
Note: The target framework is .NET 4.7 -- perhaps there is a better/more efficient method in newer versions of .NET?
I may just lack fundamental understanding of async/tasks, but I presume I am overlooking something important.
Upvotes: 0
Views: 415
Reputation: 456637
UI elements, including ListView.ListViewItemCollection
and ListViewItem
are "thread affine." This means they can only be accessed on the UI thread.
To do background work, you should only pass non-thread-affine objects. E.g., a List<string>
, not a ListViewItemCollection
or a List<ListViewItem>
.
async void startParse_Click(object sender, EventArgs e)
{
var items = listView1.Items;
var data = /* extract List<string> from items */
await Task.Run(() => startWork(data));
}
void startWork(List<string> files)
{
int overallProgress = 0;
foreach (var file in files)
{
// slowwww work
}
}
Also, you shouldn't be using BeginInvoke
. Use IProgress<T>
with Progress<T>
instead.
Upvotes: 1
Reputation: 4002
You don't have to iterate over items in a worker thread, as switching from one item in collection to another is pretty fast and do not freezes UI. Just move you "expensive" computational work to worker thread:
private async void StartParseButtonClick(object sender, EventArgs e)
{
// disable button (we are on UI thread)
var startParseButton = sender as Button;
startParseButton.Enabled = false;
try
{
// copy just in case if someone will add new item while we iterating over
var items = listView1.Items.OfType<ListViewItem>().ToList();
foreach (var item in items)
await Parse(item); // this will be invoked in worker thread
}
finally
{
// enable button finally (we are on UI thread)
startParseButton.Enabled = true;
}
}
private async Task Parse(ListViewItem item)
{
// slowwww work (we are on worker thread)
await Task.Delay(500);
}
Upvotes: 1