This code should take a piece of information from a webpage. My problem is that it doesn't show correnctly and don't know why.
I want to make it somehow to wait for document completion without creating a function outside that one.
The actual code that I want to fix from a larger file:
public static string GetNews()
{
WebBrowser page = new WebBrowser();
string data = null;
page.Navigate(launcherScriptAddress);
page.DocumentCompleted += delegate {
data = page.Document.GetElementById("news").InnerText;
// can't return `data` from here
};
return data; // returns null because it doesn't wait for document to be completed
}
This won't work the way you try to do it. Your function returns way before the page.DocumentCompleted-delegate is being executed.
So the only thing you can do is pass a callback to your GetNews-function that gets executed within your delegate.
The old problem with sync <-> async.
Besides, you should swap the statements .Navigate and .DocumentCompleted +=
to make sure "page" has the callback set before it even starts to load anything.
[edit]
To do that, you need to create a delegate and change your function:
public delegate void NewsCallback( string dataReceived );
public static void GetNews( NewsCallback callback )
{
WebBrowser page = new WebBrowser();
string data = null;
page.Navigate(launcherScriptAddress);
page.DocumentCompleted += delegate {
data = page.Document.GetElementById("news").InnerText;
callback( data );
};
}
After that, you may want to call it this way:
void CallMyNews(){
GetNews( (dataReceived) => {
DoSomeStuffWith(dataReceived);
} );
}
Related
I'm working on an app that uses the WimgApi package by Jeff Kluge to apply a windows image to a disk.
I'm having issues getting the example callback method to update UI components, specifically a label on a form (ideally a progressbar).
I've tried to use a delegate to set the value but this does not seem to work as I cannot figure how to pass the delegate down to the callback method.
If I make the callback method non-static, I can access the form properties but then I get a deadlock that even if I disable deadlock breaking, it just locks up.
I've been told to look at using IProgress and async but whilst I can change the code to run the method asynchronously (this works and UI doesn't lock), I still cannot figure out how to get the MyCallbackMethod to send info back to the ui.
//Apply Image Method
public void ApplyImage()
{
using (WimHandle wimHandle = WimgApi.CreateFile(#"C:\osimages\test.wim",
WimFileAccess.Read,
WimCreationDisposition.OpenExisting,
WimCreateFileOptions.None,
WimCompressionType.None))
{
// Always set a temporary path
WimgApi.SetTemporaryPath(wimHandle, Environment.GetEnvironmentVariable("TEMP"));
// Register a method to be called while actions are performed by WIMGAPi for this .wim file
WimgApi.RegisterMessageCallback(wimHandle, MyCallbackMethod);
try
{
// Get a handle to the first image in the .wim file
using (WimHandle imageHandle = WimgApi.LoadImage(wimHandle, 1))
{
// Apply the image contents to C:\Apply
// This call is blocking but WIMGAPI will be calling MyCallbackMethod() during the process
WimgApi.ApplyImage(imageHandle, #"X:\", WimApplyImageOptions.None);
}
}
finally
{
// Be sure to unregister the callback method
//
WimgApi.UnregisterMessageCallback(wimHandle, MyCallbackMethod);
}
}
private static WimMessageResult MyCallbackMethod(WimMessageType messageType, object message, object userData)
{
switch (messageType)
{
case WimMessageType.Progress: // Some progress is being sent
// Get the message as a WimMessageProgress object
//
WimMessageProgress progressMessage = (WimMessageProgress)message;
// UPDATE UI
//THIS IS WHERE I WANT TO SEND BACK PROGRESS INFO
break;
//REMOVED OTHER MESSAGE CASE STATEMENTS TO CONDENSE CODE
}
// Depending on what this method returns, the WIMGAPI will continue or cancel.
//
// Return WimMessageResult.Abort to cancel. In this case we return Success so WIMGAPI keeps going
return WimMessageResult.Success;
}
//full example code at Example code is https://github.com/jeffkl/ManagedWimgApi/wiki/Message-Callbacks
If I try and access the label property in the callback method , I receive an 'object reference is required for non static field, method or property form1.progressLabel.text . I've tried to create a delegate but seem to have issues accessing the method in the call back.
I've watched several videos and tried to understand the msdn documents for delegates, callbacks and things like async / backgroundworker but I just seem to come away more confused.
Really appreciate any pointers / things I should be focusing on.
Dislaimer: I don't have any experience with the WimgApi package.
But there is an overload of the WimgApi.RegisterMessageCallback method taking an arbitrary object that will be passed to the callback.
So please try this:
WimgApi.RegisterMessageCallback(wimHandle, MyCallbackMethod, this);
and in the callback:
var form = (MyForm)userData;
if (form.InvokeRequired)
{
form.Invoke((MethodInvoker)(() => UpdateProgressUI(...)));
}
else
{
form.UpdateProgressUI(...);
}
Making some assumptions here but if you will only be showing one progress form at a time, you should be able to get away with storing a static reference to it. I.e.:
class ProgressForm
{
private static ProgressForm staticRef;
private void Form_Loaded(object sender, EventArgs e)
{
staticRef = this;
}
private void InternalCallback(uint m, IntPtr w, IntPtr l, IntPtr u)
{
// Ensure we're touching UI on the right thread
if (Dispatcher.InvokeRequired)
{
Dispatcher.Invoke(() => InternalCallback(m, w, l, u));
return;
}
// Update UI components
// ....
}
private static uint StaticCallback(uint m, IntPtr w, IntPtr l, IntPtr u)
{
staticRef?.InternalCallback(m, w, l, u);
return 0;
}
}
I have the following:
public String AttachService(string whereClauseParam)
{
//Get Client object here
Client c = new Client();
string cookieFromRequest = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Cookie];
tokenInfo.TryGetValue(cookieFromRequest, out c);
string[] arr = new string[] { };
c.AttachedServiceStatus += OnAttachedServiceStatus;
string whereClause = whereClauseParam.ToString();
//c.AttachService("binding.interface='query_em'", 8799989);
return string.Format("attached");
}
//Handler code below:
public string OnAttachedServiceStatus(Client sender, ClientServiceAttachedStatus status)
{
if (status.AttachStatus == AttachedStatus.Connected && status.ServiceAttachStatus == ServiceAttachStatus.Attached)
{
//update the Client object in Dictionary
Client c = new Client();
var ou = tokenInfo.First(x => x.Value == sender);
tokenInfo.TryGetValue(ou.Key.ToString(), out c);
tokenInfo.TryRemove(ou.Key.ToString(), out c);
tokenInfo.TryAdd(ou.Key.ToString(), sender);
string[] statusInfoT = new string[200];
statusInfoT[0] = status.ServiceId.ToString();
statusInfoT[1] = status.AttachStatus.ToString();
statusInfoT[2] = status.ServiceAttachStatus.ToString();
statusInfoT[3] = status.VirtualServiceId.ToString();
statusInfoT[4] = status.AttachToken.ToString();
statusInfo.TryAdd(ou.Key.ToString(), statusInfoT);
//update the UI with a Dispatch - TO BE DONE
}
return "Connected";
}
The above AttachService method has a handler "OnAttachedServiceStatus" attached to an event "AttachedServiceStatus".
As long as the OnAttachedServiceStatus return void, it all works well. However, i now need to have the Handler OnAttachedServiceStatus to return a string but i'm not able to attach the handler correctly.
I'm thinking of using the Func delegate but not sure how to use it.
Please Help!
First of all, signature of event handler is defined by event's delegate type. If that delegate returns void, then you cannot attach any other methods. Both parameters of method and return value should match signature of even's delegate. I believe AttachedServiceStatus uses delegate which returns void. Something like that:
public delegate void Action<T1, T2>(T1 arg1, T2 arg2)
And event is
public event Action<Client, ClientServiceAttachedStatus> AttachedServiceStatus
But what if you'll use delegate which returns value? E.g.
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2)
You can declare event as
public event Func<Client, ClientServiceAttachedStatus> AttachedServiceStatus
But it makes no sense. Because event is a delegate. When you attach handler, you are actually combining delegates, creating something like list of delegates (invocation list). This list contains all attached handlers. When you raise event, all handlers in invocation list are invoked one by one, and only result of last invoked handler is returned. Order of invokation is not determined. So you even don't know which handler returned value.
(*) Though it is still possible to get all results if you will invoke each handler manually instead of raising event. See Servy comment below
UPDATE
I want the handler "OnAttachedServiceStatus" to return a string back
to Caller "AttachService" but i cannot get the following correct
When you attach handler to event, handler is not executed. It just added to invocation list of event. Event handler will be executed when Client will raise event. So AttachService is not a caller here. Client is a caller. And you cannot return string back to AttachService. After attaching handler to event, code will exit AttachService method. Some time later event will be raised, and handler will be executed, but it will not be related to AttachService method.
I'm not sure you're understanding how events work.
Client c = new Client();
// ...
c.AttachedServiceStatus += OnAttachedServiceStatus;
OnAttachedServiceStatus is not being called here. Instead, this is telling the Client object to invoke the OnAttachedServiceStatus method whenever it raises the AttachedServiceStatus event, which may happen at any point in the future. It's like you telling a racer "When I say 'go', run as fast as you can to the finish line then tell me how many steps it took you to get there". The racer doesn't immediately start running at that point, nor do they tell you how many steps it took; they get in the ready position and wait. When you say "go", that's when they execute your instructions and start running. By the time you get their response, it's well after you gave him the instruction to wait.
From the looks of things, you're attempting to establish a remote connection and are wanting confirmation back from the server that a connection has indeed been established. If using events to convey that information, you'll want to use the EventArgs to carry it. You should be able to achieve that with something like this:
Client side:
public String ConnectToServer(string whereClauseParam)
{
//Create Server object here
Server s = new Server();
s.AttachedServiceStatus += OnAttachedServiceStatus;
s.AttachService(this, whereClauseParam, 8799989);
}
public void OnAttachedServiceStatus (object sender, ClientServiceAttachedEventArgs e)
{
if (e.AttachStatus == AttachedStatus.Connected && e.ServiceAttachStatus == ServiceAttachStatus.Attached)
{
// Update the UI with the message from the server.
MessageBox.Show(e.Message);
// If you need to do something else with the server in response, you can do this:
((Server)sender).Foo("bar");
}
}
And on the server side, define the custom EventArgs class for your event:
// By making this inherit from EventArgs, we can use the built-in EventHandler<T> delegate for the event itself.
public class ClientServiceAttachedEventArgs : EventArgs
{
public AttachedStatus AttachStatus { get; set; }
public ServiceAttachStatus ServiceAttachStatus { get; set; }
public string Message { get; set; }
// You can put in as many properties as you want to carry the information back from the server.
}
And put this in your Server class:
public event EventHandler<ClientServiceAttachedEventArgs> AttachedServiceStatus;
public String AttachService(Client client, string whereClauseParam, int code)
{
// Do what you need to do to register the client.
//...
// Assuming everything went as planned, fire the event.
// First, construct the EventArgs with information about the results of the connection.
ClientServiceAttachedEventArgs e = new ClientServiceAttachedEventArgs();
e.AttachStatus = AttachedStatus.Connected;
e.ServiceAttachStatus = ServiceAttachStatus.Attached;
e.Message = "Attached";
// This is where your OnAttachedServiceStatus method in the client finally gets called. If the event handler were returning a string, this is where it would be returned to and I can't imagine this does you any good.
AttachedServiceStatus(this, e);
}
This is a fairly basic implementation and your situation is probably more complex but it should point you in the right direction. The important thing to note is that the string that you wanted returned back to the client is coming through the event as part of the ClientServiceAttachedEventArgs, along with your status enums. This is the preferred way of sending information through events.
I create parallel process and DataTable dtUser have two rows, it should create two browser:
Parallel.ForEach(dtUser.AsEnumerable(), items =>
OpenBrowser(items["user"].ToString(), items["pass"].ToString()));
Lapsoft_OneDriver browser;
public void OpenBrowser(string username, string password)
{
browser = new Lapsoft_OneDriver(Browsers.Chrome);
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
}
It create two Chrome process but only first process running line code block:
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
The second process only initializes new browser and not do anything.
If I change this line:
browser = new Lapsoft_OneDriver(Browsers.Chrome);
to
var browser = new Lapsoft_OneDriver(Browsers.Chrome);
It's working.
But another method continues to use variable browser to execute other code.
So, I must declare global variable Lapsoft_OneDriver browser out of a function to use in another method use it.
My problem is:
Why using Lapsoft_OneDriver browser; it create two Chrome process but only first process active, it will insert to browser.FindElementById("txtUserName") two values of variable username and second process not do anything?
Updated:
When to change the code, I have any problem.
I will add more code of frmMain_Load:
private void frmMain_Load(object sender, EventArgs e)
{
thread = new LThread();
thread.StartedEvent += new LThread.startDelegate(AllCaseProgram);
numLog = int.Parse(dtSetting.Rows[0]["num_Log"].ToString());
}
int numProcess;
private void AllCaseProgram(object args)
{
try
{
switch (numProcess)
{
case 0:
Parallel.ForEach(dtUser.AsEnumerable(), items => Start(items["user"].ToString(), items["pass"].ToString()));
break;
case 1:
ClickCart();
break;
case 2:
Result();
break;
}
}
catch (Exception ex)
{
if (browser != null)
browser.Cleanup();
numProcess = 0;
AllCaseProgram(null);
}
}
At event of button StartProgram()_Click. I start Thread like: thread.Start();
You said: should be add this function to my program.
public static void Start(string user, string pwd)
{
var test = new frmMain();
test.OpenBrowser(user, pwd);
test.ClickCart();
}
My update question is:
Seem function Start(string user, string pwd) should be change to function AllCaseProgram include all switch case.
And variable numLog in frmMain_Load have values = 3. In function test.ClickCart() I also use this variable but values auto change to 0.
Have any issues with code? Thanks.
And LThread class is:
public class LThread : BackgroundWorker
{
#region Members
public delegate void startDelegate(string ID);
public event startDelegate StartedEvent;
private static int RandNumber(int Low, int High)
{
Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), System.Globalization.NumberStyles.HexNumber));
int rnd = rndNum.Next(Low, High);
return rnd;
}
protected override void OnDoWork(DoWorkEventArgs e)
{
StartedEvent(RandNumber(100,10000).ToString()); //put whatever parameter suits you or nothing
base.OnDoWork(e);
e.Result = e.Argument;
}
BackgroundWorker bwThread;
// Main thread sets this event to stop worker thread:
public Boolean bwIsRun;
int m_time_delay = 10000;
Delegate m_form_method_run;
Delegate m_form_method_stop;
Form m_type_form;
#endregion
#region Functions
public void Start()
{
try
{
bwIsRun = true;
this.RunWorkerAsync();
}
catch { }
}
public void Stop()
{
try
{
bwIsRun = false;
}
catch { }
}
private void StartToListen(object sender, DoWorkEventArgs e)
{
while (true)
{
Thread.Sleep(m_time_delay);
if (bwIsRun == true)
{
m_type_form.Invoke(m_form_method_run);
}
else
{
BackgroundWorker bwAsync = sender as BackgroundWorker;
if (bwAsync.CancellationPending)
{
e.Cancel = true;
return;
}
break;
}
}
}
#endregion
}
You should encapsulate your state for each test run. That way you'll have a class that has the responsibility the start a browser, execute one or more actions, while keeping all the required state belonging to a single run private for just one instance, while you can have a many instances as you like (if resources permit).
// this is NOT a winform, this is a new and seperate class ...
// don't try to mix this with an WinForm, that will fail
public class BrowserTestRunner
{
// only this Test instances uses this browser
Lapsoft_OneDriver browser;
private void OpenBrowser(string username, string password)
{
browser = new Lapsoft_OneDriver(Browsers.Chrome);
browser.GoToUrl(link);
browser.FindElementById("txtUserName").SendKeys(username);
browser.FindElementById("txtpassword").SendKeys(password);
// you probably want to click on something here
}
// some other test
private void ClickCart()
{
browser.FindElementById("btnCart").Click();
}
// add other actions here
// this starts the test for ONE browser
public static void Start(string user, string pwd)
{
var runner = new BrowserTestRunner();
runner.OpenBrowser(user, pwd);
// wait for stuff, check data, prepare the next steps
// for example
// runner.ClickCart();
// other actons here
}
}
Now you can create as many Test class instances as you like, while each instance of the class manages its own internal state, without interfering with other instances:
Parallel.ForEach(dtUser.AsEnumerable(), items =>
BrowserTestRunner.Start(items["user"].ToString(), items["pass"].ToString()));
If you want to start that from your backgroundworker do:
private void AllCaseProgram(object args)
{
try
{
switch (numProcess)
{
case 0:
Parallel.ForEach(
dtUser.AsEnumerable(),
items => BrowserTestRunner.Start(items["user"].ToString(), items["pass"].ToString()));
break;
case 1:
ClickCart();
break;
case 2:
Result();
break;
}
}
catch (Exception ex)
{
if (browser != null)
browser.Cleanup();
numProcess = 0;
AllCaseProgram(null);
}
}
By all means: don't start the main form again. Just separate your WinForm from the code you use to operate the browser. That does mean that you have to move the code that interacts with the browser to the BrowserTestRunner. Don't try in keeping the logic for your selenium stuff in the WinForm class because that is doomed to fail. As you are already experiencing.
What you got here is sort of a race condition. You got two threads not getting along when handling a single field in the class. Your problem is only that you don't have sufficient space to store all the browser instances you require.
What happens is basically that the first thread enters the method, creates a instance of the chrome browser and stores it in the variable. Then the second thread enters the function and does the same thing. But it also stores the instance in the same variable. Now the first thread continues and goes to a link. But the instance it is working with is already replaced by the second thread. And so on. This may happen with the threads the other way around or the overlapping may happen after more lines where handled. But it is bound to go wrong.
The way to resolve it, is as you noticed to make the variable local by adding a var. This way both threads are working with distinct variables.
Now you said you need the variable in another function. The question is: Do you need both? Do you need only one? Do you need a specific one?
In case you need only one, you just store the variable in the global variable by adding a line like this in your function:
this.browser = browser;
So it would look like this in total:
Lapsoft_OneDriver browser;
public void OpenBrowser(string username, string password)
{
var localBrowser = new Lapsoft_OneDriver(Browsers.Chrome);
localBrowser.GoToUrl(link);
localBrowser.FindElementById("txtUserName").SendKeys(username);
localBrowser.FindElementById("txtpassword").SendKeys(password);
this.browser = localBrowser;
}
I changed the name of the local browser variable, so it gets clearer what variable is used. Do note that either one of the created browsers could end up in the variable.
In case you need a specific one you have to determine if you have the correct one and store the result after this.
If you need both you have to store them in a list. The namespace System.Collections.Concurrent offers lists that can be handled by multiple threads at once.
I have this code;
Button button = new Button();
MessageBox ms = new MessageBox(button);
Action<bool> action = ms.Show();
action += (b) =>
{
Console.WriteLine(b.ToString()); //this isnt working
Console.WriteLine("??");
};
Console.Read();
button.OnClick();
Console.ReadKey();
MessageBox class :
class MessageBox
{
Button _button;
public MessageBox(Button button) { _button = button; }//initialize button
public Action<bool> Show()
{
Action<bool> action = new Action<bool>(CallForTest);
_button.OnClick+=()=>{ action?.Invoke(true); };
return action;
}
//...working.*//
public void CallForTest(bool statu){}
}
I want to return an action and when button is clicked,call the action.But this isnt working? What is the problem? Action is a delegate so delegate is a reference type?(compiler generated class) What is wrong in this picture?
I think when "Show()" is ends,"action" is collected from gargabe collector.But this is working with other reference types? for example;
public Test Show()
{
Test test = new Test("??");
button.OnClick += () =>
{
test.JustForTest(); //working (cw("?????" + ctorvalue);
};
return test;
}
Delegates are immutable. When you are combining two delegates using +=, you are actually creating a new delegate. So when you have done act += ... in the above code, you have actually created a new delegate, it is different from what you have already created in Show() method.
I believe this is happening because when you use += to a delegate it does not append to the internal list. This is why you don't see b.string() being printed
Without changing your design you won't be able to append the action to the original delegate when the button is clicked.
What you are actually writing is somthing like:
var act2 = new Action<bool>((b) =>
{
Console.WriteLine(b.ToString()); //this isnt working
Console.WriteLine("??");
});
var act = act + act2;
as you can see act is getting a new reference to the combined expression of act + act2 rather than act itself concatenating act2 internally.
if you do act(false) you will see the extra results, but not if you invoke the button click.
What you should be using is event on the delegate within the Button, which is the way UI controls are written
class Button
{
public event EventHandler<BoolEventArgs> Click;
}
best to read up on using events when you want to have multicast delegates in this way. MSDN site
I've got a C# program that talks to an instrument (spectrum analyzer) over a network. I need to be able to change a large number of parameters in the instrument and read them back into my program. I want to use backgroundworker to do the actual talking to the instrument so that UI performance doesn't suffer.
The way this works is - 1) send command to the instrument with new parameter value, 2) read parameter back from the instrument so I can see what actually happened (for example, I try to set the center frequency above the max that the instrument will handle and it tells me what it will actually handle), and 3) update a program variable with the actual value received from the instrument.
Because there are quite a few parameters to be updated I'd like to use a generic routine. The part I can't seem to get my brain around is updating the variable in my code with what comes back from the instrument via backgroundworker. If I used a separate RunWorkerCompleted event for each parameter I could hardwire the update directly to the variable. I'd like to come up with a way of using a single routine that's capable of updating any of the variables. All I can come up with is passing a reference number (different for each parameter) and using a switch statement in the RunWorkerCompleted handler to direct the result. There has to be a better way.
I think what I would do is pass a list of parameters, values, and delegates to the BackgroundWorker. That way you can write the assign-back code "synchronously" but have execution deferred until the values are actually retrieved.
Start with a "request" class that looks something like this:
class ParameterUpdate
{
public ParameterUpdate(string name, string value, Action<string> callback)
{
this.Name = name;
this.Value = value;
this.Callback = callback;
}
public string Name { get; private set; }
public string Value { get; set; }
public Action<string> Callback { get; private set; }
}
Then write your async code to use this:
private void bwUpdateParameters_DoWork(object sender, DoWorkEventArgs e)
{
var updates = (IEnumerable<ParameterUpdate>)e.Argument;
foreach (var update in updates)
{
WriteDeviceParameter(update.Name, update.Value);
update.Value = ReadDeviceParameter(update.Name);
}
e.Result = updates;
}
private void bwUpdateParameters_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
var updates = (IEnumerable<ParameterUpdate>)e.Argument;
foreach (var update in updates)
{
if (update.Callback != null)
{
update.Callback(update.Value);
}
}
}
Here's how you would kick off the update. Let's say you've got a bunch of member fields that you want to update with the actual values of the parameters that were used:
// Members of the Form/Control class
private string bandwidth;
private string inputAttenuation;
private string averaging;
// Later on, in your "update" method
var updates = new List<ParameterUpdate>
{
new ParameterUpdate("Bandwidth", "3000", v => bandwidth = v),
new ParameterUpdate("InputAttenuation", "10", v => inputAttenuation = v),
new ParameterUpdate("Averaging", "Logarithmic", v => averaging = v)
};
bwUpdateParameters.RunWorkerAsync(updates);
That's all you have to do. All of the actual work is done in the background, but you're writing simple variable-assignment statements as if they were in the foreground. The code is short, simple, and completely thread-safe because the actual assignments are executed in the RunWorkerCompleted event.
If you need to do more than this, such as update controls in addition to variables, it's very simple, you can put anything you want for the callback, i.e.:
new ParameterUpdate("Bandwidth", "3000", v =>
{
bandwidth = v;
txtBandwidth.Text = v;
})
Again, this will work because it's not actually getting executed until the work is completed.
[Edit - look back at update history to see previous answer. Talk about not being able to see the wood for the trees]
Is there any reason that, rather than passing a reference number to the Background Worker, you can't pass the ID of the label that should be updated with any value passed back?
So the UI adds an item in the work queue containing:
Variable to change
Attempted change
UI ID
and the BackgroundWorker triggers an event with EventArgs containing
Attempted change
Actual value after attempt
UI ID
Error Message (null if successful)
which is all the information you need to update your UI without a switch or multiple event args and without your Background Worker ever being aware of UI detail.
How about something like this?
[TestFixture]
public class BGWorkerTest
{
string output1;
string output2;
[Test]
public void DoTest()
{
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, args) =>
{
output1 = DoThing1();
output2 = DoThing2();
};
backgroundWorker.RunWorkerAsync();
//Wait for BG to finish
Thread.Sleep(3000);
Assert.AreEqual("Thing1",output1);
Assert.AreEqual("Thing2",output2);
}
public string DoThing1()
{
Thread.Sleep(1000);
return "Thing1";
}
public string DoThing2()
{
Thread.Sleep(1000);
return "Thing2";
}
}