In the code below, I have a List object. I want to iterate through each one and assign a value to one of the properties on each Item object. To get the value, I need to call an async method of a WCF service.
When the call to my WCF service completes, how do I take that data and assign it to the current instance (i) in itemsList? Is there a way to access i from my xxxCompleted event?
private void SomeMethod()
{
List<Item> itemsList = GetItems();
foreach(Item i in itemsList)
{
MyClient client = new MyClient();
client.GetSomeValueCompleted += client_GetSomeValueCompleted;
client.GetSomeValueAsync(i.ID);
}
}
private void client_GetSomeValueCompleted(object sender, GetSomeValueEventArgs e)
{
int id = e.Result;
// how do I assign this ID to my itemsList object, i ???
}
You can pass the instance of MyClient class as the userstate on the async method call.
Take a look at this link
private void SomeMethod()
{
List itemsList = GetItems();
foreach(Item i in itemsList)
{
MyClient client = new MyClient();
client.GetSomeValueCompleted += client_GetSomeValueCompleted;
client.GetSomeValueAsync(i.ID, client);
}
}
private void client_GetSomeValueCompleted(object sender, GetSomeValueEventArgs e)
{
int id = e.Result;
// how do I assign this ID to my itemsList object, i ???
(e.UserState as MyClient).ID = id;
}
Related
in my WPF - C# application, I have a time consuming function, which I execute with a BackgroundWorker. The job of this function is to add given data from a file into a database. Now and then, I need some user feedback, for example the data is already in the store and I want to ask the user, whether he wants to merge the data or create a new object or skip the data completely. Much like the dialog windows shows, if I try to copy a file to a location, where a file with the same name already exists.
The problem is, that I cannot call a GUI-window from a non GUI-thread. How could I implement this behavior?
Thanks in advance,
Frank
You could work with EventWaitHandle ou AutoResetEvent, then whenever you want to prompt the user, you could the signal UI, and then wait for the responde. The information about the file could be stored on a variable.
If possible... my suggestion is to architect your long running task into atomic operations. Then you can create a queue of items accessible by both your background thread and UI thread.
public class WorkItem<T>
{
public T Data { get; set; }
public Func<bool> Validate { get; set; }
public Func<T, bool> Action { get; set; }
}
You can use something like this class. It uses a queue to manage the execution of your work items, and an observable collection to signal the UI:
public class TaskRunner<T>
{
private readonly Queue<WorkItem<T>> _queue;
public ObservableCollection<WorkItem<T>> NeedsAttention { get; private set; }
public bool WorkRemaining
{
get { return NeedsAttention.Count > 0 && _queue.Count > 0; }
}
public TaskRunner(IEnumerable<WorkItem<T>> items)
{
_queue = new Queue<WorkItem<T>>(items);
NeedsAttention = new ObservableCollection<WorkItem<T>>();
}
public event EventHandler WorkCompleted;
public void LongRunningTask()
{
while (WorkRemaining)
{
if (_queue.Any())
{
var workItem = _queue.Dequeue();
if (workItem.Validate())
{
workItem.Action(workItem.Data);
}
else
{
NeedsAttention.Add(workItem);
}
}
else
{
Thread.Sleep(500); // check if the queue has items every 500ms
}
}
var completedEvent = WorkCompleted;
if (completedEvent != null)
{
completedEvent(this, EventArgs.Empty);
}
}
public void Queue(WorkItem<T> item)
{
// TODO remove the item from the NeedsAttention collection
_queue.Enqueue(item);
}
}
Your UI codebehind could look something like
public class TaskRunnerPage : Page
{
private TaskRunner<XElement> _taskrunner;
public void DoWork()
{
var work = Enumerable.Empty<WorkItem<XElement>>(); // TODO create your workItems
_taskrunner = new TaskRunner<XElement>(work);
_taskrunner.NeedsAttention.CollectionChanged += OnItemNeedsAttention;
Task.Run(() => _taskrunner.LongRunningTask()); // run this on a non-UI thread
}
private void OnItemNeedsAttention(object sender, NotifyCollectionChangedEventArgs e)
{
// e.NewItems contains items that need attention.
foreach (var item in e.NewItems)
{
var workItem = (WorkItem<XElement>) item;
// do something with workItem
PromptUser();
}
}
/// <summary>
/// TODO Use this callback from your UI
/// </summary>
private void OnUserAction()
{
// TODO create a new workItem with your changed parameters
var workItem = new WorkItem<XElement>();
_taskrunner.Queue(workItem);
}
}
This code is untested! But the basic principle should work for you.
Specifically to your case
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(1000);
var a = Test1("a");
Thread.Sleep(1000);
var b = (string)Invoke(new Func<string>(() => Test2("b")));
MessageBox.Show(a + b);
}
private string Test1(string text)
{
if (this.InvokeRequired)
return (string)this.Invoke(new Func<string>(() => Test1(text)));
else
{
MessageBox.Show(text);
return "test1";
}
}
private string Test2(string text)
{
MessageBox.Show(text);
return "test2";
}
Test2 is a normal method which you have to invoke from background worker. Test1 can be called directly and uses safe pattern to invoke itself.
MessageBox.Show is similar to yourForm.ShowDialog (both are modal), you pass parameters to it (text) and you return value (can be a value of property of yourForm which is set when form is closed). I am using string, but it can be any data type obviously.
From the input of the answers here, I came to the following solution:
(Mis)Using the ReportProgress-method of the Backgroundworker in Combination with a EventWaitHandle. If I want to interact with the user, I call the ReportProgress-method and setting the background process on wait. In the Handler for the ReportProgress event I do the interaction and when finished, I release the EventWaitHandle.
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
}
// Starting the time consuming operation
private void Button_Click(object sender, RoutedEventArgs e)
{
bgw.RunWorkerAsync();
}
// using the ProgressChanged-Handler to execute the user interaction
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UserStateData usd = e.UserState as UserStateData;
// UserStateData.Message is used to see **who** called the method
if (usd.Message == "X")
{
// do the user interaction here
UserInteraction wnd = new UserInteraction();
wnd.ShowDialog();
// A global variable to carry the information and the EventWaitHandle
Controller.instance.TWS.Message = wnd.TextBox_Message.Text;
Controller.instance.TWS.Background.Set();
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
// our time consuming operation
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// need 4 userinteraction: raise the ReportProgress event and Wait
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
// The WaitHandle was released, the needed information should be written to global variable
string first = Controller.instance.TWS.Message.ToString();
// ... and again
Thread.Sleep(2000);
bgw.ReportProgress(0, new UserStateData() { Message = "X", Data = "Test" });
Controller.instance.TWS.Background.WaitOne();
e.Result = first + Controller.instance.TWS.Message;
}
I hope I did not overlooked some critical issues. I'm not so familar with multithreading - maybe there should be some lock(object) somewhere?
I am using a button to call a method available in my Sub class from a Main class
How can I retrieve result of type myObject gotten when calling executeMethodDelegate() inside Sub class
if that method must register event first and wait until is complete?
While debugging I noticed that when sub_myClick is executed it does not wait the
event called inside executeMethodDelegate() So everything is computed the right way but can not
use result in Main class...
Main
void sub_myClick(object sender, EventArgs e)
{
Sub.executeMethodDelegate(); //I have object Sub available with all its functions
}
Inside Sub I execute an event that must interact with user to allow him pick an object of type myObject themethod is getPicked
inside executeMethodDelegate I register event LeftButtonReleaseEvt like:
public void executeMethodDelegate()
{
myInteractorStyle pick = myInteractorStyle.New();
pick.LeftButtonReleaseEvt += (s, args) => getPicked(OnLeftButtonUp(s, args));
//here it will register and do the event to select an object
//how can I return myObject result to main class
//when getPicked finishs it would return myObject but do not know how to return it to
}
private myObject OnLeftButtonUp(object sender, EventArgs e)
{
//compute some stuff
//...
//...
//create object of type myObject named result
return result;
}
public myObject getPicked(myObject result)
{
//result is manipulated given certain conditions that is why the anonymous function
//then would like to return result of type myObject to executeMethodDelegate()
return result;
}
So my problem is How to recover event result and send it back to Main class
How can I retrieve and send result to Main class?
Your Sub class can expose a GotPicked event which contains myObject in its EventArgs and then yoi van register to it in your Main class
Main:
public Main()
{
Sub.GotPicked += OnGotPicked
}
void sub_myClick(object sender, EventArgs e)
{
Sub.executeMethodDelegate(); //I have object Sub available with all its functions
}
public void OnGotPicked(object sender, SubEventArgs e)
{
// Do something with myObject
var myObject = e.MyObject;
}
public class SubEventArgs : EventArgs
{
public SubEventArgs(MyObject obj)
{
MyObject = obj;
}
public readonly MyObject MyObject { get; set; }
}
Sub:
public event EventHandler<SubEventArgs> GotPicked;
public void getPicked(myObject result)
{
// Do stuff woth result
GotPicked(this, new SubEventArgs(result);
I am trying to get two types of data from two different websites and binding it to a list but I'm having a problem with the Async, what I want to do is get info from a rss add it to a list , then get info from another website add it to a list then add the two to a bound observable collection. But the DownloadStringAsync are over running each otherand the app crashes. Can you help me please?
my code is
private static ObservableCollection<Top> top= new ObservableCollection<Top>();
private static ObservableCollection<string> place= new ObservableCollection<string>();
// Constructor
public MainPage()
{
InitializeComponent();
if (NetworkInterface.GetIsNetworkAvailable())
{
LoadSiteContent_A(url1);
LoadSiteContent_B(url2);
}
else
MessageBox.Show("No Internet Connection, please connect to use this applacation");
listBox.ItemsSource = top; //trying to bind listbox after web calls
}
public void LoadSiteContent_A(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += new DownloadStringCompletedEventHandler(a_DownloadStringCompleted);
clientC.DownloadStringAsync(new Uri(url));
}
public void LoadSiteContent_B(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += new DownloadStringCompletedEventHandler(b_DownloadStringCompleted);
clientC.DownloadStringAsync(new Uri(url));
}
public void a_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
string testString = "";
if (!e.Cancelled && e.Error == null)
{
string str;
str = (string)e.Result;
//Various operations and parsing
place.Add(testString);
}
}
}
public void b_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
string testMatch = "";
if (!e.Cancelled && e.Error == null)
{
string str;
// Size the control to fill the form with a margin
str = (string)e.Result;
//Various operations and parsing
top.Add(new Top(testMatch,(place.Count+1)));
}
}
public class TopUsers
{
public string TopUsername { get; set; }
public int Place { get; set; }
public TopUsers(string topusername, int place)
{
this.TopUsername = topusername;
this.Place = place;
}
}
}
I would not try to make them one after the other like that. By "stacking" them one after the other like that you are losing all the advantages of the asynchronous calls in the first place. Not only that, but on a mobile platform like Windows Phone you have to remember that network calls get queued up for efficient use of the antenna. When you make both the calls simultaneously they have a much higher chance of being executed during the same antenna connection which is a "Good Thing".
Next, each of your callbacks are actually updating completely independent collections. A is updating the place collection and B is updating the top collection. So it's not a matter of these two stepping on each other in any way.
The only real problem I see here is simply that you're updating the top collection which is set as the listBox.ItemsSource. You need to marshal updates to bound data back to the UI thread (aka Dispatcher thread)so that the controls that are bound to them will be updating on the correct thread.
So the only change you should have to make to any of your code is to marshal the addition of the new item to the top collection back to the Dispatcher thread in the B callback. That would look like this:
public void b_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
string testMatch = "";
if(!e.Cancelled && e.Error == null)
{
string str;
// Size the control to fill the form with a margin
str = (string)e.Result;
//Various operations and parsing
Top newTop = new Top(testMatch,(place.Count+1));
Dispatcher.Invoke(() =>
{
top.Add(newTop);
});
}
}
With this, all your work remains async/concurrent except for the tiny little part where you add the item to the top collection.
This is more of an alternate answer (AlexTheo's solution should work).
All this gets a lot easier when they give us (WP Developers) the new Async stuff.
Your code could be written like this:
public async MainPage()
{
InitializeComponent();
DoAsyncLoad();
}
private async Task DoAsyncLoad() // note use of "async" keyword
{
if (NetworkInterface.GetIsNetworkAvailable())
{
await LoadSiteContent_A("");
await LoadSiteContent_B("");
}
else
MessageBox.Show("No Internet Connection, please connect to use this applacation");
listBox.ItemsSource = top; //trying to bind listbox after web calls
}
public async Task LoadSiteContent_A(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
var result = await clientC.DownloadStringTaskAsync(new Uri(url));
// No need for a Lambda or setting up an event
var testString = result; // result is the string you were waiting for (will be empty of canceled or errored)
}
public async Task LoadSiteContent_B(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
var result = await clientC.DownloadStringTaskAsync(new Uri(url));
// Again, no need for a Lambda or setting up an event (code is simpler as a result)
top.Add(new Top(testMatch, place.Count + 1));
}
There are more code changes you would have to make (using the Async versions of the Http calls and marking the LoadSiteContent_A/B as async --and setting a return of Task).
BTW, you can actually load the latest Async-CTP3 and release WP code that is written this way. Most people are a little scared of a CTP though.
I wrote a blog post on this that you can check out here -- http://www.jaykimble.net/metro-nuggets-async-is-your-friend.aspx
First of all I believe that a lamdba will be better than the callback in your case.
In order to synchronize the downloading you have to call the LoadSiteContent_B in the complete event of LoadSiteContent_A.
private static ObservableCollection<Top> top= new ObservableCollection<Top>();
private static ObservableCollection<string> place= new ObservableCollection<string>();
private string _url1;
private string _url2;
// Constructor
public MainPage(string url1, string url2)
{
InitializeComponent();
if (NetworkInterface.GetIsNetworkAvailable())
{
_url1 = url1;
_url2 = url2;
LoadSiteContent_A(url1);
}
else
MessageBox.Show("No Internet Connection, please connect to use this applacation");
listBox.ItemsSource = top; //trying to bind listbox after web calls
}
public void LoadSiteContent_A(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += (sender, e) => {
string testString = "";
if (!e.Cancelled && e.Error == null)
{
string str;
str = (string)e.Result;
//Various operations and parsing
place.Add(testString);
LoadSiteContent_B(_url2);
}
};
clientC.DownloadStringAsync(new Uri(url));
}
public void LoadSiteContent_B(string url)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += (sender, e) => {/*do whatever you need*/};
clientC.DownloadStringAsync(new Uri(url));
}
public class TopUsers
{
public string TopUsername { get; set; }
public int Place { get; set; }
public TopUsers(string topusername, int place)
{
this.TopUsername = topusername;
this.Place = place;
}
}
}
Even with the lambda, there is a much more elegant solution - use a custom Action, where T is the data type.
For example:
public void LoadSiteContent_A(string url, Action<string> onCompletion)
{
//create a new WebClient object
WebClient clientC = new WebClient();
clientC.DownloadStringCompleted += (s,e) =>
{
onCompletion(e.Result);
};
clientC.DownloadStringAsync(new Uri(url));
}
When you are calling this method, you could pass an action like this:
LoadSiteContent_a(yourUrlWhatever, data =>
{
// DO SOMETHING WITH DATA
});
Let's say I want to sent an int parameter to a background worker, how can this be accomplished?
private void worker_DoWork(object sender, DoWorkEventArgs e) {
}
I know when this is worker.RunWorkerAsync();, I don't understand how to define in worker_DoWork that it should take an int parameter.
You start it like this:
int value = 123;
bgw1.RunWorkerAsync(argument: value); // the int will be boxed
and then
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
int value = (int) e.Argument; // the 'argument' parameter resurfaces here
...
// and to transport a result back to the main thread
double result = 0.1 * value;
e.Result = result;
}
// the Completed handler should follow this pattern
// for Error and (optionally) Cancellation handling
private void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
// check error, check cancel, then use result
if (e.Error != null)
{
// handle the error
}
else if (e.Cancelled)
{
// handle cancellation
}
else
{
double result = (double) e.Result;
// use it on the UI thread
}
// general cleanup code, runs when there was an error or not.
}
Even though this is an already answered question, I'd leave another option that IMO is a lot easier to read:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (obj, e) => WorkerDoWork(value, text);
worker.RunWorkerAsync();
And on the handler method:
private void WorkerDoWork(int value, string text) {
...
}
You can pass multiple arguments like this.
List<object> arguments = new List<object>();
arguments.Add("first"); //argument 1
arguments.Add(new Object()); //argument 2
// ...
arguments.Add(10); //argument n
backgroundWorker.RunWorkerAsync(arguments);
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
List<object> genericlist = e.Argument as List<object>;
//extract your multiple arguments from
//this list and cast them and use them.
}
You can use the DoWorkEventArgs.Argument property.
A full example (even using an int argument) can be found on Microsoft's site:
How to: Run an Operation in the Background
Check out the DoWorkEventArgs.Argument Property:
...
backgroundWorker1.RunWorkerAsync(yourInt);
...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
// Extract the argument.
int arg = (int)e.Argument;
// Start the time-consuming operation.
e.Result = TimeConsumingOperation(bw, arg);
// If the operation was canceled by the user,
// set the DoWorkEventArgs.Cancel property to true.
if (bw.CancellationPending)
{
e.Cancel = true;
}
}
you can try this out if you want to pass more than one type of arguments, first add them all to an array of type Object and pass that object to RunWorkerAsync() here is an example :
some_Method(){
List<string> excludeList = new List<string>(); // list of strings
string newPath ="some path"; // normal string
Object[] args = {newPath,excludeList };
backgroundAnalyzer.RunWorkerAsync(args);
}
Now in the doWork method of background worker
backgroundAnalyzer_DoWork(object sender, DoWorkEventArgs e)
{
backgroundAnalyzer.ReportProgress(50);
Object[] arg = e.Argument as Object[];
string path= (string)arg[0];
List<string> lst = (List<string>) arg[1];
.......
// do something......
//.....
}
You need RunWorkerAsync(object) method and DoWorkEventArgs.Argument property.
worker.RunWorkerAsync(5);
private void worker_DoWork(object sender, DoWorkEventArgs e) {
int argument = (int)e.Argument; //5
}
You should always try to use a composite object with concrete types (using composite design pattern) rather than a list of object types. Who would remember what the heck each of those objects is? Think about maintenance of your code later on... Instead, try something like this:
Public (Class or Structure) MyPerson
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public int ZipCode { get; set; }
End Class
And then:
Dim person as new MyPerson With { .FirstName = “Joe”,
.LastName = "Smith”,
...
}
backgroundWorker1.RunWorkerAsync(person)
and then:
private void backgroundWorker1_DoWork (object sender, DoWorkEventArgs e)
{
MyPerson person = e.Argument as MyPerson
string firstname = person.FirstName;
string lastname = person.LastName;
int zipcode = person.ZipCode;
}
Like the title says:
My web service method call looks like
proxy.BeginGetWhatever(int param)
{
}
Lets assume the handler registered with this call is
private void GetWhateverCompleted(object sender, GetWhateverEventArgs e)
{
//HERE
}
How do I get access to the parameter param in the handler? (e.Result will return whatever the web service call is supposed to fetch. I am interested in making param available as well)
Each async method generated for a WCF proxy will have an overload that takes a userState parameter. For example, if you have a GetCustomerByID method, you'll see two overloads:
public void GetCustomerByIDAsync(Guid customerID) { ... }
public void GetCustomerByIDAsync(Guid customerID, object userState { ... }
You can put whatever you want in userState and it will be sent back in the completion event. So if you just want the original customerID back, in the above case:
public void BeginGetCustomerByID(Guid customerID)
{
// Second instance of customerID is userState
service.GetCustomerByIDAsync(customerID, customerID);
}
private void service_GetCustomerByIDCompleted(object sender,
GetCustomerByIDCompletedEventArgs e)
{
Guid customerID = (Guid)e.UserState;
// Do something with e.Error or e.Result here
}
You can put anything you want in userState, so if the method takes several parameters, you can put them all into a custom class and pass the class as the state.
Your web method would have to return it somehow. Let's say your webmethod is to return the original parameter and some other data back to the caller, here's the web method signature:
CustomReturnClass GetWhatever(int param);
And the definition of CustomReturnClass defined in the web service.
public class CustomReturnClass
{
public int OrigParameter { get; set; }
public object OtherStuff { get; set; }
}
Then in your callback (traditional style) you would have:
private void GetWhateverCompleted(IAsynchResult res)
{
CustomReturnClass retVal = (CustomReturnClass)res.EndGetWhatever(res);
int origParam = retVal.OrigParameter;
}
It looks, however, like you're using WCF, so it would be more like this:
private void GetWhateverCompleted(object sender, GetWhateverEventArgs e)
{
CustomReturnClass retVal = e.Result;
int origParam = retVal.OrigParameter;
}