Convert Event based code to Rx - c#

I have the following code (simplified for posting purposes).
public class SomeDataObject
{
public delegate void ReadyEventHandler;
public delegate void ErrorEventHandler;
public event ReadyEventHandler Ready;
public event ErrorEventHandler Error;
...
}
pubic class ConsumerClass
{
private SomeDataObject dataObject;
private Task<List<string>> GetStrings()
{
List<string> results = new List<string>();
var tcs = new TaskCompletionSource<List<string>>();
SomeDataObject.ReadyEventHandler ReadyHandler = null;
SomeDataObject.ErrorEventHandler ErrorHandler = null;
ReadyHandler += () =>
{
for (int i =0; i < dataObject.ItemCount; i++)
results.Add(dataObject[i].ToString());
tcs.TrySetResult(results);
}
ErrorHandler += ()
{
tcs.TrySetException(new Exception("oops!");
}
dataObject.Ready += ReadyHandler;
dataObject.Error += ErrorHandler;
dataObject.DoRequest();
}
}
The idea is that when DoRequest call is made, SomeDataObject will get some data and raise either the Ready or Error events (details not important!). If data is available, then the ItemCount indicates how many items are available.
I am new to Rx and cannot find any comparable example. So is it possible to convert this into Rx so that IObservable<string> is returned instead of Task<List<string>> using Observable.Create somehow?
Regards
Alan

Matthew's answer is close but has some problems. First, it is eager, which is not normally in the spirit of Rx/Functional programming. Next I think that you will want to be able to release the event handles when the consumer disposes. Finally the usage of a subject should be a code smell, and this case it points to the two problems above :-)
Here I use Observable.Create (which should be your #1 goto tool in the tool box, with subjects being your last resort) to lazily connect, and also offer disconnection/releasing events when the subscription is disposed.
private IObservable<string> GetStrings()
{
return Observable.Create<string>(o=>
{
SomeDataObject.ReadyEventHandler ReadyHandler = null;
SomeDataObject.ErrorEventHandler ErrorHandler = null;
ReadyHandler += () =>
{
for (int i =0; i < dataObject.ItemCount; i++)
o.OnNext(dataObject[i].ToString());
o.OnCompleted();
}
ErrorHandler += () =>
{
o.OnError(new Exception("oops!"));
}
dataObject.Ready += ReadyHandler;
dataObject.Error += ErrorHandler;
dataObject.DoRequest();
return Disposable.Create(()=>
{
dataObject.Ready -= ReadyHandler;
dataObject.Error -= ErrorHandler;
});
}
}
I would also consider moving dataObject to a parameter to the method too. Sharing state in an Async system is a source of problems.

In response to your comments on Lee's (perfectly lovely and tick-worthy) answer, here's how to modify his answer to get a single List<string> response and block for it:
private IObservable<List<string>> GetStrings(SomeDataObject dataObject)
{
return Observable.Create<List<string>>(o=>
{
SomeDataObject.ReadyEventHandler ReadyHandler = null;
SomeDataObject.ErrorEventHandler ErrorHandler = null;
ReadyHandler = () =>
{
var results = new List<string>(dataObject.ItemCount);
for (int i =0; i < dataObject.ItemCount; i++)
results.Add(dataObject[i].ToString());
o.OnNext(results);
o.OnCompleted();
};
ErrorHandler = () =>
{
o.OnError(new Exception("oops!"));
};
dataObject.Ready += ReadyHandler;
dataObject.Error += ErrorHandler;
dataObject.DoRequest();
return Disposable.Create(()=>
{
dataObject.Ready -= ReadyHandler;
dataObject.Error -= ErrorHandler;
});
});
}
Now you can block on this with:
var results = GetStrings().Wait();
If using .NET 4.5, then in an async method you can also do:
var results = await GetStrings();

I think the code below will do what you want. A ReplaySubject is used to ensure that the caller gets all of the results, even if the SomeDataObject events start immediately.
private IObservable<string> GetStrings()
{
ReplaySubject<string> results = new ReplaySubject<string>();
SomeDataObject.ReadyEventHandler ReadyHandler = null;
SomeDataObject.ErrorEventHandler ErrorHandler = null;
ReadyHandler += () =>
{
for (int i =0; i < dataObject.ItemCount; i++)
results.OnNext(dataObject[i].ToString());
results.OnCompleted();
}
ErrorHandler += ()
{
results.OnError(new Exception("oops!"));
}
dataObject.Ready += ReadyHandler;
dataObject.Error += ErrorHandler;
dataObject.DoRequest();
return results;
}

Related

How can i check if i catch a exception in thread?

I have an abstract class which runs threads:
protected volatile bool HasError = false;
public void Run()
{
var readingThread = new Thread(ReadInFile);
var compressingThreads = new List<Thread>();
for (var i = 0; i < Environment.ProcessorCount; i++)
{
var j = i;
ProcessEvents[j] = new AutoResetEvent(false);
compressingThreads.Add(new Thread(() => Process(j)));
}
var writingThread = new Thread(WriteOutFile);
readingThread.Start();
foreach (var compressThread in compressingThreads)
{
compressThread.Start();
}
writingThread.Start();
WaitHandle.WaitAll(ProcessEvents);
OutputDictionary.SetCompleted();
writingThread.Join();
Console.WriteLine(!HasError ? "Successfully competed" : "Error");
}
Well, and I don't know how I can check the Exception?
This is class realizes abstract class.
This is one method :
protected override void Process(int processEventId)
{
try
{
while (InputQueue.Dequeue(out Chunk chunk) && !HasError)
{
var compressedChunk = GZip.GZip.CompressByBlocks(chunk.Bytes);
OutputDictionary.Add(chunk.Id, compressedChunk);
}
ProcessEvents[processEventId].Set();
}
catch (Exception e)
{
HasError = true;
}
}
As you can see, I change the value of a variable when I catch an exception, but will it work? I do not understand how to check.
The better answer is probably not to use Threads but use Parallel.For(), it manages your errors and also has better options to handle workload.
But in your current setup, just add a wrapper method:
var j = i;
ProcessEvents[j] = new AutoResetEvent(false);
compressingThreads.Add(new Thread(() => SafeCallProcess(j) ));
and
private void SafeCallProcess(int j)
{
try
{
Process (j);
}
catch(Exception e)
{
// deal with it
}
}
You could move the Set() to the wrapper too, up to your taste I guess.

Google url scraping not looping lines in list box

I am trying to scrape results from google search, the tool need to browse pages one by one. However the issue is its not taking the all the list from the listbox. Its just working for the first line of the list box.
Startbtn Code
foreach (string url in urlList.Items)
{
webBrowser1.Navigate("https://www.google.com/search?q=" + url);
await PageLoad(30, 5);
MessageBox.Show("sdsaD3");
string pageSource = webBrowser1.DocumentText;
Scrape(pageSource);
}
--
Scrape Method
private async void Scrape(string pageSource)
{
string regexExpression = "(?<=><div class=\"rc\"><div class=\"r\"><a href=\")(.*?)(?=\" onmousedown=)";
Regex match = new Regex(regexExpression, RegexOptions.Singleline);
MatchCollection collection = Regex.Matches(pageSource, regexExpression);
for (int i = 0; i < collection.Count; i++)
{
CommonCodes.WriteToTxt(collection[i].ToString(), "googlescrapedurls.txt");
if (i == collection.Count - 1)
{
var elementid = webBrowser1.Document.GetElementById("pnnext");
if (elementid != null)
{
for (int w = 0; w < 1; w++)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
worker.RunWorkerAsync(w);
}
}
else if(webBrowser1.Document.GetElementById("pnnext") == null)
{
for(int pg=0; pg< urlList.Items.Count; pg++)
{
webBrowser1.Navigate("https://www.google.com/search?q=" + urlList.Items[pg+1]);
CommonCodes.WaitXSeconds(10);
//await PageLoad(30, 5);
Scrape(webBrowser1.DocumentText);
}
}
}
}
--
Background worker code:
BackgroundWorker backgroundWorker = sender as BackgroundWorker;
webBrowser1.Invoke(new Action(() => { gCaptcha(); }));
webBrowser1.Invoke(new Action(() => { webBrowser1.Document.GetElementById("pnnext").InvokeMember("Click"); }));
await PageLoad(30, 5);
webBrowser1.Invoke(new Action(() => { Scrape(webBrowser1.DocumentText); }));
pageload code
try
{
TaskCompletionSource<bool> PageLoaded = null;
PageLoaded = new TaskCompletionSource<bool>();
int TimeElapsed = 0;
webBrowser1.DocumentCompleted += (s, e) =>
{
if (webBrowser1.ReadyState != WebBrowserReadyState.Complete) return;
if (PageLoaded.Task.IsCompleted) return; PageLoaded.SetResult(true);
};
//
while (PageLoaded.Task.Status != TaskStatus.RanToCompletion)
{
await Task.Delay(delay * 1000);//interval of 10 ms worked good for me
TimeElapsed++;
if (TimeElapsed >= TimeOut * 100) PageLoaded.TrySetResult(true);
}
}
catch (Exception ex)
{
CommonCodes.WriteLog(ex.ToString());
MessageBox.Show(ex.Message);
}
--
The main problem is when I have 5 lines in listbox, for the first line only it is going to every page and scraping urls but for the other lines its not working properly. I don't understand the problem with in the code. Some how the code
MessageBox.Show("sdsaD3");
executing multiple time(If 5 lines in listbox then this msg boxpoping up 5 times). Thanks for the help.
EDit: I found the issue, it seems the issue is with await PageLoad(30, 5); but I am not sure how to invoke async method. Any one have idea?

Unsubscribe event in reactive extension, Execute a continuasoly

public IDisposable subscription;
public ProcessData(DeviceModel model, string name = "") : base(model, name)
{
BaseAddress = _model.Process.oxyVal.regAddr;
CurrentSampleRate=1000;
subscription = Observable.Interval(TimeSpan.FromMilliseconds(CurrentSampleRate)).Subscribe(async t => await Refresh());
}
public async Task Refresh()
{
Status = "Fetching from device...";
if ((_model == null) || !_model.IsSerialPortOpen || busy)
{
StatusTrans = "Device not connected.";
return;
}
busy = true;
try
{
await ReadFromDevice().ConfigureAwait(false);
StatusTrans = "Completed.";
}
catch (Exception ex)
{
StatusTrans = ex.Message;
Trace.WriteLine(ex.Message);
}
busy = false;
}
protected override async Task ReadFromDevice()
{
var SampleRate_var = await _model.Measurement.sampleRate.sampleRate.GetValue();
CurrentSampleRate = SampleRate_var * 1000;
/*****other code********/
}
public void ChangeSampleRate()
{
subscription?.Dispose();
Observable.Interval(TimeSpan.FromMilliseconds(CurrentSampleRate)).Subscribe(async t => await Refresh());
}
I have been trying to fetching details from device.On the start,i'll subscribe a event 1 sec,in the it try to read from device(ReadFromDevice) and i'll change get the CurrentSampleRate,Onproperty change,that time I'll dispose 1sec subscribe event and i will subscribe new event with current sample rate.But problem is ReadFromDevice happens at every 1 sec.
Try this:
var CurrentSampleRate = 30;
Observable
.Generate(0, x => true, x => x + 1, x => x, x => TimeSpan.FromMilliseconds(CurrentSampleRate))
.Subscribe(x =>
{
if (x == 50)
{
CurrentSampleRate = 1000;
}
Console.Write(".");
});
It's a little nasty in that you're changing some external state to make the observable's interval change - so you may need to use Interlocked.Exchange to make the update safe.

C#: Task Factory starts task repeatedly?

I have a probably simple question about the task factory. I have to following code:
In this task is a loop that is polling data from the RS232 and a counter that stops polling after 10 times. After this "doCollect" will be set to false.
And now comes the strange thing: The task runs repeatedly. The caller code is:
// class Main()
RS232DataAquisition _RS232DataAquisition = new RS232DataAquisition();
public override void Run()
{
System.Diagnostics.Stopwatch timeout = new System.Diagnostics.Stopwatch();
timeout.Start();
_RS232DataAquisition.Start();
while ((timeout.ElapsedMilliseconds <= (dataGatherTime_inSeconds * 1000)) && _RS232DataAquisition.DoCollect)
{
System.Threading.Thread.Sleep(100);
}
timeout.Stop();
_RS232DataAquisition.Stop();
}
Per my understanding the Run() function should start the thread and return into the while-loop waiting for the thread to finish. But it never does?!
Here's the code for ReadDataFromRS232:
// sealed class RS232DataAquisition
private bool doCollect = false;
public bool DoCollect
{
get { return doCollect; }
}
public void Start()
{
doCollect = true;
currentTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
this.ReadDataFromRS232();
});
}
private void ReadDataFromRS232(int NumtoRead = 10)
{
var port = new System.IO.Ports.SerialPort(PortName);
int waitCount = 5;
var portExists = System.IO.Ports.SerialPort.GetPortNames().Any(x => x == PortName);
if (!portExists)
{
throw new ArgumentException("Port does not exist!");
}
while (port.IsOpen && waitCount-- > 0)
{
doCollect = false;
Wait();
}
doCollect = true;
if (!port.IsOpen)
{
port.Open();
port.NewLine = _NewLine;
port.ReadTimeout = 2000;
int number;
try { }
finally { }
port.Write("flashon\r");
while (doCollect && (_readCounter <= NumtoRead))
{
string s;
try
{
s = port.ReadLine();
}
catch
{
s = "-1";
}
int i;
if (int.TryParse(s, out i))
{
number = Convert.ToInt32(s, 10);
}
else
{
number = 0;
}
lock (thisLock) _data.Add(number);
_readCounter++;
}
port.Write("flashoff\r");
port.Close();
port.Dispose();
Wait(); Wait();
}
}
private void Wait()
{
System.Threading.Thread.Sleep(10);
System.Threading.Thread.SpinWait(1);
}
I don't get, why "ReadDataFromRS232" is beeing repeated until the timeout stops this task.
Thank you for any help :)
EDIT: Added some missing code.
As Dennis said the problem seemed to come from the missing volatile. It works now even though I have no idea why it didn't before.

Unit Testing NAudio with ManualResetEvent Locks Up Test

Alright, I'm trying to unit test NAudio against a wrapper I created for a recording session, here is the code that starts and stops a recording session ...
public void StartRecording(string claimNo, string ip_no, string ip_name)
{
if (this.IsRecording)
{
return;
}
this.Recordings.Add(new RecordingTrack(claimNo, ip_no, ip_name));
if (this.MicrophoneLevel == default(float))
{
this.MicrophoneLevel = .75f;
}
_aggregator.Reset();
_input = new WaveIn();
_input.WaveFormat = _waveFormat;
_input.DataAvailable += (s, args) =>
{
_writer.Write(args.Buffer, 0, args.BytesRecorded);
byte[] buffer = args.Buffer;
for (int index = 0; index < args.BytesRecorded; index += 2)
{
short sample = (short)((buffer[index + 1] << 8) | buffer[index + 0]);
float sample32 = sample / 32768f;
_aggregator.Add(sample32);
}
if (this.DataAvailable != null)
{
this.DataAvailable(s, args);
}
if (!this.IsRecording)
{
_writer.Close();
_writer.Dispose();
_writer = null;
}
};
_input.RecordingStopped += (s, args) =>
{
_input.Dispose();
_input = null;
if (this.RecordingStopped != null)
{
this.RecordingStopped(s, args);
}
};
_writer = new WaveFileWriter(this.CurrentRecording.FileName, _input.WaveFormat);
_input.StartRecording();
this.IsRecording = true;
}
public void StopRecording()
{
if (!this.IsRecording)
{
return;
}
this.CurrentRecording.Stop();
this.IsRecording = false;
_input.StopRecording();
}
... and below is my unit test. I'm using a ManualResetEvent to assert the success of the event being fired and it's declared like this ...
private ManualResetEvent _eventRaised = new ManualResetEvent(false);
... however, the issue is that the test below simply locks up and the event is never fired. Can you confirm that the issue is that the WaitOne is not allowing the event to fire because it's locking the same thread?
bool success = false;
_eventRaised.Reset();
var target = new RecordingSession();
target.StartRecording("1", "01", "Test Name");
target.RecordingStopped += (s, args) =>
{
success = (target.CurrentRecording.Duration.TotalSeconds > 4);
_eventRaised.Set();
};
Thread.Sleep(4000);
target.StopRecording();
_eventRaised.WaitOne();
Assert.IsTrue(success);
And if so, can you help me with this test? I need some enlightenment.
I've used the ManualResetEvent many times to test events on other classes and it's worked, but something is different here.
You'll never get an event because the default constructor of WaveIn uses windowed callbacks, and you are not running your unit test on a GUI thread. You should use WaveInEvent instead to work on a non-gui thread. In the very latest NAudio code an InvalidOperationException should be thrown to prevent you from making this mistake.
It is possible that the event is fired before you have connected to it if the _input member is working in its own thread. Hence, the manual reset event will never get set, causing it to wait forever/lock up your unit test.
So perhaps try re-order your definition to:
target.RecordingStopped += (s, args) =>
{
success = (target.CurrentRecording.Duration.TotalSeconds > 4);
_eventRaised.Set();
};
target.StartRecording("1", "01", "Test Name");

Categories

Resources