C# MVVM pattern update UI in Infinite loop thread? [duplicate] - c#

This question already has an answer here:
Lifeupdate DataGrid from TextFile with good performance
(1 answer)
Closed 5 years ago.
I have a background thread to listen data, so this infinite loop to output data.
And, I using a ObservableCollection to bind a ListBox and show log.
I try to use Dispather.BeginInvoke, but no use, it not realtime. I have no idea for update UI in MVVM.
xaml
<ListBox x:Name="lsb_log" Width="auto" ItemsSource="{Binding DisplayLogs}"/>
code
private ObservableCollection<string> _displayLogs;
public ObservableCollection<string> DisplayLogs
{
get
{
return _displayLogs;
}
}
public Testpro()
{
_displayLogs = new ObservableCollection<string>();
Task.Run(new Action(outputData));
}
private void outputData()
{
string str = "";
while (true)
{
string newString = GetInfoString();
if (string.IsNullOrWhiteSpace(newString) || str == newString)
continue;
str = newString;
Debug.WriteLine(str);
Application.Current.Dispatcher.BeginInvoke((Action)delegate ()
{
DisplayLogs.Add(str);
});
}
}
*Edit:
GetInfoString will call by a dll file.

You should really use something designed for this kind of update. I'm going to suggest Microsoft's Reactive Framework (NuGet "System.Reactive" & "System.Reacive.Windows.Threading").
Then you can do this after the _displayLogs = new ObservableCollection<string>(); line:
IDisposable subscription =
Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Select(_ => GetInfoString())
.Where(newString => !string.IsNullOrWhiteSpace(newString))
.DistinctUntilChanged()
.ObserveOnDispatcher()
.Subscribe(newString =>
{
Debug.WriteLine(newString);
DisplayLogs.Add(newString);
});
That should call GetInfoString() every second and update your UI. It handles all of the threading and marshalling issues.
If you keep a reference to subscription you can stop it at any stage by calling subscription.Dispose().

You can use async-await therefore. Your OutputData-Method can look something like:
private async void OutputData(IProgress<string> onProgress)
{
await Task.Factory.StartNew(() =>
{
string str = string.Empty;
while (true)
{
string newString = GetInfoString();
if(string.IsNullOrWhiteSpace(newString) || str == newString)
continue;
str = newString;
// call the UI to update your bound collection
onProgress.Report(str);
// give the ui some time to respond before continue with your endless-loop
Thread.Sleep(200);
}
});
}
And you can call this method with:
OutputData(new Progress<string>((str) =>
{
DisplayLogs.Add(str);
}));
That's it

Related

How to fix the error: the 'dataGridView1' control was accessed from a different thread [duplicate]

This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
Closed 1 year ago.
Good morning everyone, I'm having trouble with rewriting a DataGrid, the function works, but I would need to do a faster search, so the thought was to add parallelism.
But upon applying the latter it generates an error for me: System.InvalidOperationException: 'Invalid cross-thread operation: the control 'dataGridView1' was accessed from a different thread than the one from which the creation was performed.'
The problem is clear to me, however I can't figure out how to solve it. Could you guys please help me out?
I've already tried applying Invoke but the program goes into an infinite loop.
private void inputSearch_TextChanged(object sender, EventArgs e)
{
Parallel.For(0, 7, i =>
{
Ricerca(i);
});
}
private void Ricerca(int i)
{
string searchValue = inputSearch.Text.ToUpper();
var re = from row in dataTable.AsEnumerable()
where
row[i].ToString().Contains(searchValue)
select row;
if (re.Count() != 0)
{
Invoke(new Action(() =>
{
dataGridView1.DataSource = re.CopyToDataTable();
dataGridView1.Columns[7].Visible = false;
}));
}
}
You have a deadlock because you are blocking the UI thread in inputSearch_TextChanged method (that is invoked by UI thread).
If your intent is to parallelize the Linq expression, split Ricerca method in two. The final part of the method should be invoked out of the parallel for, maybe directly on inputSearch_TextChanged.
A better solution could be to use .NET Task instead.
Otherwise, if you don't need parallel at all, you can replace it with a simple for.
I was able to solve the problem as suggested by Cherry by saving the content in another table and recopying it later.
private void inputSearch_TextChanged(object sender, EventArgs e)
{
Parallel.For(0, 7, i =>
{
Ricerca(i);
});
dataGridView1.DataSource = table;
dataGridView1.Columns[7].Visible = false;
}
private void Ricerca(int i)
{
string searchValue = inputSearch.Text.ToUpper();
var re = from row in dataTable.AsEnumerable()
where
row[i].ToString().Contains(searchValue)
select row;
if (re.Count() != 0)
{
table = re.CopyToDataTable();
}
}
Thanks to all!

Waiting for a result before continuing and without blocking UI [duplicate]

This question already has answers here:
Is async always asynchronous in C#? [duplicate]
(1 answer)
Do you have to put Task.Run in a method to make it async?
(3 answers)
async method in C# not asynchronous?
(3 answers)
Closed 5 years ago.
I have a TextBox with a TextChanged event that calls a custom event if the text of the textbox represents an existing file. In this event, there is a call to an outside dll that does some processing on the File, which can take upwards of a minute to finish. There is also some post-processing I do, dependent on what result this method returns to me. Currently, this is blocking my UI, which is highly undesirable.
There are essentially 2 "options"/scenarios I see.
Within the custom event, somehow wait for the dll call to finish, before continuing the event, while also keeping the UI free. This seems like the simplest idea from my multithreading-untrained self, but it also conceptually throws red flags at me: Is this even possible given that the custom event itself (called from TextChanged) is on the UI thread?
Throw the entire custom event into it's own thread using Task.Run(). Downside here is that apart from the dll method call, there is quite a good amount of UI elements that are affected by getters/setters after the long method. I could write alternated getters/setters based on the appropriate InvokeRequired, but if there is a more correct way to do this, I'd rather take that approach.
I made a much shorter (although contrived) example project, which shows essentially what I'm after, using option 2 from above:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
comboBox1.Items.Add("Select One...");
comboBox1.Items.Add("Item 1");
comboBox1.Items.Add("Item 2");
Value = 0;
}
public string SetMessage
{
set
{
if (lblInfo.InvokeRequired)
lblInfo.BeginInvoke((MethodInvoker)delegate () { lblInfo.Text = Important ? value + "!" : value; });
else
lblInfo.Text = Important ? value + "!" : value;
}
}
public bool Important
{
get
{
return chkImportant.Checked;
}
set
{
if (chkImportant.InvokeRequired)
chkImportant.BeginInvoke((MethodInvoker) delegate() { chkImportant.Checked = value; });
else
chkImportant.Checked = value;
}
}
public SomeValue Value
{
get
{
if (comboBox1.InvokeRequired)
{
SomeValue v = (SomeValue)comboBox1.Invoke(new Func<SomeValue>(() => SomeValue.Bar));
return v;
}
else
{
switch (comboBox1.SelectedIndex)
{
case 1:
return SomeValue.Foo;
case 2:
return SomeValue.Bar;
default:
return SomeValue.Nothing;
}
}
}
set
{
if (comboBox1.InvokeRequired)
{
comboBox1.BeginInvoke((MethodInvoker)delegate ()
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
});
}
else
{
switch (value)
{
case SomeValue.Nothing:
comboBox1.SelectedIndex = 0;
break;
case SomeValue.Foo:
comboBox1.SelectedIndex = 1;
break;
case SomeValue.Bar:
comboBox1.SelectedIndex = 2;
break;
}
}
}
}
private void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public bool ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and return
//an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
//Do I *need* to multithread the whole thing?
Task.Run(() => CustomEvent(this, new EventArgs()));
}
}
public enum SomeValue
{
Nothing = 0,
Foo = 100,
Bar = 200
}
Note: I'm not asking for code review on my option 2 code. Rather, I'm asking if option 2 is necessary to accomplish, since that option causes me to change a considerably larger portion of code, given that it's only 1 method within it holding up the entire process.
I also realize I can simplify some of the code in these properties to prevent replication. For the sake of demonstrating to myself and debugging, I am holding off on that at this time.
Here is what I had related to option 1 (left out duplicate code and the getters/setters without their invokes):
private async void CustomEvent(object sender, EventArgs e)
{
if (!Important)
Important = true;
SetMessage = "Doing some stuff";
if (Value == SomeValue.Foo)
Debug.WriteLine("Foo selected");
//I don't want to continue until a result is returned,
//but I don't want to block UI either.
if (await ReturnsTrueEventually())
{
Debug.WriteLine("True!");
}
Important = false;
SetMessage = "Finished.";
}
public async Task<bool> ReturnsTrueEventually()
{
//Simulates some long running method call in a dll.
//In reality, I would interpret an integer and
//return an appropriate T/F value based on it.
Thread.Sleep(5000);
return true;
}
This is basically what you want. I'm violating a couple best-practices here, but just showing it's not that complicated. One thing to keep in mind is that the user can now click this button multiple times in a row. You might consider disabling it before processing. Or you can do a Monitor.TryEnter() to make sure it's not already running.
private async void buttonProcess_Click(object sender, RoutedEventArgs e)
{
textBlockStatus.Text = "Processing...";
bool processed = await Task.Run(() => SlowRunningTask());
}
private bool SlowRunningTask()
{
Thread.Sleep(5000);
return true;
}

Why my code wont work without Dispatcher.RunAsync in Geofencing [duplicate]

This question already has answers here:
Accessing UI (Main) Thread safely in WPF
(3 answers)
Closed 8 years ago.
why this code wont work without Dispatcher.RunAsync and what does it do? without Dispatcher its throwing error at copying value to textv.Text " thats its on different thread"
async void Current_GeofenceStateChanged(GeofenceMonitor sender, object args)
{
var reports = sender.ReadReports();
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (var report in reports)
{
GeofenceState st = report.NewState;
Geofence gf2 = report.Geofence;
if (st == GeofenceState.Entered)
{
textv2.Text = "Hello"; //XAML TEXT
}
else if(st==GeofenceState.Exited)
{
textv2.Text = "Bye";
}
}
});
}
The Event Current_GeofenceStateChanged is being fired outside of the GUI thread and only the GUI thread can change GUI elements. Dispatcher.RunAsync says the code inside should run on the GUI thread so it works.
if you put the result on a string variable it will work if you only put:
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => textv2.Text = StringVariable;);
EDIT: I only noticed that you have XAML code later you can just put the string on a property and bind the property to the text value of the text box letting you free from the Dispatcher.
<TextBox Text="{Binding StringVariable}"/>
and on the code just have
public string StringVariable { get; set; }
than on the method just set the value to the property
StringVariable = "bla bla";

Mixed use of Task and Dispatcher halts the task

Explanation
I'm creating my own search control in WPF. This control is a UserControl that contains an area with search parameters (eg.: search on specific ID, name,...) and a GridView that shows the result.
In my control I have a dependency property of type ICommand where I bind the command to execute my search query.
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
Use of my control in a certain window:
<customControls:SearchControl SearchCommand="{Binding SearchItemsCommand}"
SearchResult="{Binding SearchResult}" />
SearchItemsCommand is a Command in my ViewModel where I can find my search query.
In this command you can find my query to retrieve the result.
SearchResult is my ICollection that contains the result of the search query.
Code of the commands
Viewmodel
private DelegateCommand searchItemsCommand;
public DelegateCommand SearchItemsCommand
{
get
{
if (this.searchItemsCommand== null)
this.searchItemsCommand= new DelegateCommand(this.SearchItemsCommandExecuted);
return this.searchItemsCommand;
}
}
private ICollection<VoucherOverviewModel> voucherResults;
private void SearchItemsCommandExecuted()
{
using (DbContext context = new DbContext())
{
var query = (from v in context.Vouchers
join vt in context.VoucherTransactions on new
{
voucherID = v.VoucherID,
type = VoucherTransactionType.Out
} equals new
{
voucherID = vt.VoucherID,
type = vt.Type
}
join vtype in context.VoucherTypes on v.VoucherTypeID equals vtype.VoucherTypeID
join c in context.Customers on vt.CustomerID equals c.CustomerID
join pos in context.PointOfSales on v.PointOfSaleID equals pos.PointOfSaleID
select new VoucherOverviewModel()
{
PointOfSaleID = v.PointOfSaleID,
PointOfSaleName = pos.Name,
VoucherID = v.VoucherID,
VoucherCode = v.Code,
VoucherTypeID = v.VoucherTypeID,
VoucherTypeDescription = vtype.Code,
CustomerID = c.CustomerID,
CustomerName = c.Name,
Value = vt.Value,
UsedValue = context.VoucherTransactions
.Where(x => x.VoucherID == v.VoucherID &&
x.Type == VoucherTransactionType.In)
.Sum(x => x.Value),
CreateDate = vt.Date,
ValidFrom = v.ValidFrom,
ValidUntil = v.ValidUntil,
ParentVoucherID = v.ParentVoucherID,
Comment = v.Comment,
});
foreach (ISearchParameter searchParameter in this.SearchParameters)
{
if (!searchParameter.Value.IsNullOrDefault())
{
switch ((FilterVoucherParameterKey)searchParameter.Key)
{
case FilterVoucherParameterKey.CustomerID:
query = query.Where(x => x.CustomerID == (int)searchParameter.Value);
break;
case FilterVoucherParameterKey.VoucherID:
query = query.Where(x => x.VoucherCode.Contains((string)searchParameter.Value));
break;
case FilterVoucherParameterKey.PointOfSale:
query = query.Where(x => x.PointOfSaleID == (byte)searchParameter.Value);
break;
case FilterVoucherParameterKey.Type:
query = query.Where(x => x.VoucherTypeID == (byte)searchParameter.Value);
break;
}
}
}
this.voucherResults = query.ToList();
}
}
Custom control
public static readonly DependencyProperty SearchCommandProperty =
DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl));
public ICommand SearchCommand
{
get
{
return (ICommand)this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
This is my dependency property so that I can bind the SearchItemsCommand to my Custom control.
Then I have another ICommand to execute the binded command and show the loading element in my custom control.
This LocalSearchCommand will be executed when you click on a button.
private DelegateCommand localSearchCommand;
public DelegateCommand LocalSearchCommand
{
get
{
if (this.localSearchCommand == null)
this.localSearchCommand = new DelegateCommand(this.LocalSearchCommandExecuted);
return this.localSearchCommand;
}
}
private void LocalSearchCommandExecuted()
{
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
}
The problem
I want to show a Loading element when the query is executing to interact with the user. To show this element, I have to set it visible.
The problem now is, when I set it visible and want to execute the search command, my whole UI freezes. After the result is fetched from the database and generated in the GridView, then, and only then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.Dispatcher.Invoke((Action)(() => this.SearchCommand.Execute(null)));
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
t.Dispose();
}
});
I have to use the Dispatcher in my Task to execute the SearchCommand, because it is owned by the UI-thread.
But because of the use of the Dispatcher class, I have the same problem as before. My loading element is only shown when the query is already executed, because the Dispatcher executes the search command back on the UI-thread.
Without the use of the Dispatcher class, it gives me the following error:
The calling thread cannot access this object because a different thread owns it.
I get this error on the line:
return (ICommand)this.GetValue(SearchCommandProperty);
Even with an empty SearchItemsCommandExecuted method, the error occurs.
What I already tried
I tried setting the TaskScheduler of the Task to
TaskScheduler.FromCurrentSynchronizationContext()
I used a lot of combinations of BeginInvoke and Invoke.
I tried to set the Visibility of the loading element in the Task.
But none of the above did work.
How can I solve my problem, so that the loading element is shown when the query is executing. Did I miss something obvious?
Thanks in advance!
Loetn
The problem is that you are creating a new Task with a ThreadPool thread, but using Dispatcher.Invoke, which runs your command on the UI Thread, hence why your UI is freezing.
You need to offload your SearchCommand work to a background thread, then update your UI with a Continuation on the UI Thread (Dont try updating your UI inside SearchCommand):
then, it shows my loading element. I do understand why this happens and I tried to solve it using a Task.
loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
return this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
MyUIElement = t.Result; // Update your UI here.
}, TaskScheduler.FromCurrentSynchronizationContext());
Edit: Did not catch your binding of the first command to the second. So the following will proably not work. Looking into it...
EDIT 2: I assumed you want to start the background operation from your viewmodel. In the moment i can't think of another way than to make your loadingItem.Visible property a dependency property, move the background operation to your viewmodel, assign a property which is bound to loadingItem.Visible from there and remove the asynchronus stuff from your usercontrol.
You want to start your query on the background thread and assign the result to your ui thread:
private void LocalSearchCommandExecuted(object obj)
{
//can also be your loadingItem.
VisibleElement.Visibility = Visibility.Collapsed;
//get the ui context
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() =>
{
//do your query on the background thread
LongRunningOperation();
})
//this happens on the ui thread because of the second parameter scheduler
.ContinueWith(t =>
{
if (t.IsCompleted)
{
VisibleElement.Visibility = Visibility.Visible;
//assign the result from the LongRunningOperation to your ui list
_list = new List<string>(_tempList);
//if you need to...
RaisePropertyChanged("SearchResults");
}
}, scheduler );
}
private void LongRunningOperation()
{
//assign your result to a temporary collection
//if you do not do that you will get an exception: An ItemsControl is inconsistent with its items source
_tempList = new List<string>();
for (int i = 0; i < 100; i++)
{
_tempList.Add("Test" + i);
Thread.Sleep(10);
}
}
I solved my problem with the help of this blog.
What I had to do is to edit the getter of my Dependency property SearchCommand so that it uses the Dispatcher.
public ICommand SearchCommand
{
get
{
return (ICommand)this.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
(DispatcherOperationCallback)delegate { return this.GetValue(SearchCommandProperty); },
SearchCommandProperty);
// Instead of this
// return this.GetValue(SearchCommandProperty);
}
set
{
this.SetValue(SearchCommandProperty, value);
}
}
And this is my Command method:
private void LocalSearchCommandExecuted()
{
this.loadingElement.Visible = true;
Task.Factory.StartNew(() =>
{
this.SearchCommand.Execute(null);
})
.ContinueWith(t =>
{
if (t.IsCompleted)
{
this.Dispatcher.BeginInvoke((Action)(() => this.loadingElement.Visible= false));
t.Dispose();
}
});
}
Thanks for all the help!

How to abort specific thread in task parallel library with C#

I have this tricky task I've been trying to achieve for quiet sometime but till now I couldn't think of anything to make it work. anyway here is the scenario...
I have a winform application contains a listview and a button.
the listview contains 1 column which holds the data I need to pass to my functions later on. the column contains lets say 50 rows containing a list of links.
Now I have this function which I'm using to fetch and grab the contents of these links (5 links at a time) with parallel multithreaded mode using (Task Parallel Library):
//List<int> currentWorkingItem //contains the indices of the items in listview
//List<string> URLsList //contains the URLs of the items in listview
Parallel.ForEach(URLsList, new ParallelOptions() { MaxDegreeOfParallelism = 5 }, (url, i, j) =>
{
//show to user this link is currently being downloaded by highlighting the item to green...
this.BeginInvoke((Action)(delegate()
{
//current working item
mylistview.Items[currentWorkingItem[(int)j]].BackColor = green;
}));
//here I download the contents of every link in the list...
string HtmlResponse = GetPageResponse(url);
//do further processing....
});
Now the above code works perfectly... but sometimes I want the user to abort certain thread which is currently running and continue with the rest of the threads in the list... is that achievable in this? if so please help me out.. I'd really appreciate any solution or suggestions..
Try using Task library with cancellation tokens. I find it more elegant and safer approach to do your thing. Here is a quote good example of doing that:
using System;
using System.Threading.Tasks;
using System.Threading;
namespace CancelTask
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press 1 to cancel task");
var cTokenSource = new CancellationTokenSource();
// Create a cancellation token from CancellationTokenSource
var cToken = cTokenSource.Token;
// Create a task and pass the cancellation token
var t1 = Task<int>.Factory.StartNew(()
=> GenerateNumbers(cToken), cToken);
// to register a delegate for a callback when a
// cancellation request is made
cToken.Register(() => cancelNotification());
// If user presses 1, request cancellation.
if (Console.ReadKey().KeyChar == '1')
{
// cancelling task
cTokenSource.Cancel();
}
Console.ReadLine();
}
static int GenerateNumbers(CancellationToken ct)
{
int i;
for (i = 0; i < 10; i++)
{
Console.WriteLine("Method1 - Number: {0}", i);
Thread.Sleep(1000);
// poll the IsCancellationRequested property
// to check if cancellation was requested
if (ct.IsCancellationRequested)
{
break;
}
}
return i;
}
// Notify when task is cancelled
static void cancelNotification()
{
Console.WriteLine("Cancellation request made!!");
}
}
}
Original article could be found here: http://www.dotnetcurry.com/ShowArticle.aspx?ID=493
ok after struggling with this I finally found an efficient and an easy solution for this..
it required me only a hashtable which contains the indicies of the selected items in the listview and a simple bool value. the index is the key and the bool (true, false) is the value. the bool value is like an (on/off) switch indicates that the current loop is aborted or not.. so in order to abort specific thread simple I need to pass the key(the index) of the selected item on my listview to the foreach loop and check if the bool switch is on or off and that's basically it...
so my final code will be like this:
//I declared the hashtable outside the function so I can manage it from different source.
private Hashtable abortingItem;
Now when I click grab button it should fill the hashtable with the selected indicies...
abortingItem = new Hashtable();
for (int i = 0; i < myURLslist.SelectedItems.Count(); i++)
{
//false means don't abort this.. let it run
abortingItem.Add(myURLslist.SelectedItems[i].index, false);
}
//here should be the code of my thread to run the process of grabbing the URLs (the foreach loop)
//..........................
now if I need to abort specific item all I need is to select the item in the listview and click abort button
private void abort_Click(object sender, EventArgs e)
{
if (abortingItem != null)
{
for (int u = 0; u < myURLslist.SelectedIndices.Count; u++)
{
//true means abort this item
abortingItem[myURLslist.SelectedIndices[u]] = true;
}
}
}
In my foreach loop all I need is a simple if else statement to check if the bool is on or off:
//List<int> currentWorkingItem //contains the indices of the items in listview
//List<string> URLsList //contains the URLs of the items in listview
Parallel.ForEach(URLsList, new ParallelOptions() { MaxDegreeOfParallelism = 5 }, (url, i, j) =>
{
//aborting
if (!(bool)abortingItem[currentWorkingItem[(int)j]])
{
//show to user this link is currently being downloaded by highlighting the item to green...
this.BeginInvoke((Action)(delegate()
{
//current working item
mylistview.Items[currentWorkingItem[(int)j]].BackColor = green;
}));
//here I download the contents of every link in the list...
string HtmlResponse = GetPageResponse(url);
//do further processing....
}
else
{
//aborted
}
});
that's simply it..

Categories

Resources