I write async file reader working with specific state option. I need get notice when all files have been read, but this observable object "reader" never completes. (couldn't get "Done" notification after "reader.Wait()" operator). Could you help me understand why? How I can complete it manually?
class AsyncReader
{
public enum States { Processing, Stopped, Paused};
private Subject<string[]> filesProvider = new Subject<string[]>();
private Subject<States> state = new Subject<States>();
public void Run()
{
state.OnNext(States.Processing);
}
public IObservable<KeyValuePair<string, string>> GetDataSource()
{
return filesProvider.Select(files => ReadFiles(files, state)).Switch();
}
public AsyncReader(string[] args)
{
var reader = GetDataSource();
Observable.Start(() =>
{
reader.Wait();
Console.WriteLine("Done");
});
reader.Subscribe(line =>
{
Console.WriteLine(line);
});
filesProvider.OnNext(args);
}
public static IObservable<KeyValuePair<string, string>> ReadFile(string filePath, IObservable<States> rState) =>
rState.Where(state => state == States.Processing)
.SelectMany(_ =>
Observable
.Using(
() => new StreamReader(filePath),
reader =>
Observable
.Defer(
() =>
Observable
.FromAsync(reader.ReadLineAsync))
.Repeat()
.TakeWhile(line => line != null)
.Select(line => new KeyValuePair<string, string>(filePath, line))));
public static IObservable<KeyValuePair<string, string>> ReadFiles(string[] files, IObservable<States> readState)
{
IObservable<KeyValuePair<string, string>> dataSource = Observable.Empty<KeyValuePair<string, string>>();
foreach (var file in files)
{
dataSource = dataSource.Concat(ReadFile(file, readState));
}
return dataSource;
}
}
Short example for use:
class Program
{
static void Main(string[] args)
{
AsyncReader reader = new AsyncReader(args);
reader.Run();
Console.ReadKey();
}
}
Related
!!warning: Rx newbie!!
We have multiple price feeds. The requirement is to subscribe to all these feeds and only output the latest tick every 1 sec(throttle)
public static class FeedHandler
{
private static IObservable<PriceTick> _combinedPriceFeed = null;
private static double _throttleFrequency = 1000;
public static void AddToCombinedFeed(IObservable<PriceTick> feed)
{
_combinedPriceFeed = _combinedPriceFeed != null ? _combinedPriceFeed.Merge(feed) : feed;
AddFeed(_combinedPriceFeed);
}
private static IDisposable _subscriber;
private static void AddFeed(IObservable<PriceTick> feed)
{
_subscriber?.Dispose();
_subscriber = feed.Buffer(TimeSpan.FromMilliseconds(_throttleFrequency)).Subscribe(buffer => buffer.GroupBy(x => x.InstrumentId, (key, result) => result.First()).ToObservable().Subscribe(NotifyClient));
}
public static void NotifyClient(PriceTick tick)
{
//Do some action
}
}
The code have multiple issues. If I call AddToCombinedFeed with the same feed multiple times, the streams will get duplicated to start with. Eg. below
IObservable<PriceTick> feed1;
FeedHandler.AddToCombinedFeed(feed1);//1 stream
FeedHandler.AddToCombinedFeed(feed1);//2 streams(even though the groupby and first() functions will prevent this effect to propagate further
This brings me to the question. If I want to remove one price stream from the merged stream, how can I do that?
Update - New Solution
With Dynamic-Data (MIT-License) from RolandPheasant with Nuget.
Use a SourceList instead of a List
Use the MergeMany operator
Code:
public class FeedHandler
{
private readonly IDisposable _subscriber;
private readonly SourceList<IObservable<PriceTick>> _feeds = new SourceList<IObservable<PriceTick>>();
private readonly double _throttleFrequency = 1000;
public FeedHandler()
{
var combinedPriceFeed = _feeds.Connect().MergeMany(x => x).Buffer(TimeSpan.FromMilliseconds(_throttleFrequency)).SelectMany(buffer => buffer.GroupBy(x => x.InstrumentId, (key, result) => result.First()));
_subscriber = combinedPriceFeed.Subscribe(NotifyClient);
}
public void AddFeed(IObservable<PriceTick> feed) => _feeds.Add(feed);
public void NotifyClient(PriceTick tick)
{
//Do some action
}
}
Old Solution
Eradicate the need to resubscribe by applying Switch() technique.
Your _combinedPriceFeed just switches to the next observable that
will be supplied by _combinePriceFeedChange.
Keep a list to manage your multiple feeds. Create the new observable whenever the list changes and provide it via _combinePriceFeedChange.
You should get the logic of an corresponding remove method.
Code:
public class FeedHandler
{
private readonly IDisposable _subscriber;
private readonly IObservable<PriceTick> _combinedPriceFeed;
private readonly List<IObservable<PriceTick>> _feeds = new List<IObservable<PriceTick>>();
private readonly BehaviorSubject<IObservable<PriceTick>> _combinedPriceFeedChange = new BehaviorSubject<IObservable<PriceTick>>(Observable.Never<PriceTick>());
private readonly double _throttleFrequency = 1000;
public FeedHandler()
{
_combinedPriceFeed = _combinedPriceFeedChange.Switch().Buffer(TimeSpan.FromMilliseconds(_throttleFrequency)).SelectMany(buffer => buffer.GroupBy(x => x.InstrumentId, (key, result) => result.First()));
_subscriber = _combinedPriceFeed.Subscribe(NotifyClient);
}
public void AddFeed(IObservable<PriceTick> feed)
{
_feeds.Add(feed);
_combinedPriceFeedChange.OnNext(_feeds.Merge());
}
public void NotifyClient(PriceTick tick)
{
//Do some action
}
}
Here's the code you need:
private static SerialDisposable _subscriber = new SerialDisposable();
private static void AddFeed(IObservable<PriceTick> feed)
{
_subscriber.Disposable =
feed
.Buffer(TimeSpan.FromMilliseconds(_throttleFrequency))
.SelectMany(buffer =>
buffer
.GroupBy(x => x.InstrumentId, (key, result) => result.First()))
.Subscribe(NotifyClient);
}
I've created a download manager, but as far as download manager stores the download arguments, it's working good.
Downloader downloader = new Downloader();
downloader.Add(new List<Query>(){
new Query(dataset.trans_sprzedaz_pozycje, () => dataset.PobierzTransSprzedazPozycja(downloader.Filter)),
new Query(dataset.trans_sprzedaz_rabaty_dodatkowe, () => dataset.PobierzDokumentySprzedazyRabaty(downloader.Filter)),
new Query(dataset.trans_sprzedaz_teksty, () => dataset.PobierzDokumentySprzedazyTeksty(downloader.Filter))
});
But in some cases I need my queries to store the arguments, so it should look like:
Downloader downloader = new Downloader();
downloader.Add(new List<Query>(){
new Query(dataset.trans_sprzedaz_pozycje, () => dataset.PobierzTransSprzedazPozycja(query.Filter)),
new Query(dataset.trans_sprzedaz_rabaty_dodatkowe, () => dataset.PobierzDokumentySprzedazyRabaty(query.Filter)),
new Query(dataset.trans_sprzedaz_teksty, () => dataset.PobierzDokumentySprzedazyTeksty(query.Filter))
});
Note that in first code snippet I use downloader.Filter, in second I use (fabricated) query.Filter
I know that I can make constructions like:
var query = new Query(dsSprzedaz.trans_sprzedaz_pozycje);
query.AddFunc(() => dsSprzedaz.PobierzTransSprzedazPozycja(query.Filter));
But adding 20-30 queries like this would be terrible.
I tried sollution like this:
Filter filter;
new Query(dsSprzedaz.trans_sprzedaz_pozycje,() => dsSprzedaz.PobierzTransSprzedazPozycja(filter), out filter),
but it copy value from query.Filter, not the reference.
//EDIT
Here's the most important code of downloader:
private Task Execute()
{
var Tasks = new List<Task>(Queries.Count);
foreach (var query in Queries)
{
var task = query.Execute(CancellationToken);
Tasks.Add(task);
}
return Task.WhenAll(Tasks);
}
private void CreateFilter(List<long> id_list)
{
lock (Data)
{
Data.Clear();
Data.Append("(0");
foreach (var value in id_list)
Data.Append("," + value);
Data.Append(")");
}
}
public string Filter
{
get
{
return Data.ToString();
}
}
And Query:
public class Query
{
DataTable Source;
Func<DataTable> Download;
StringBuilder Data;
public Query(DataTable Source, Expression<Func<DataTable>> Download, out string filter)
{
this.Source = Source;
this.Data = new StringBuilder();
filter = Filter;
this.Download = Download.Compile();
}
public async Task Execute(CancellationToken cancellationToken)
{
try
{
DataTable result = await Task.Run(() => Download());
if (cancellationToken.IsCancellationRequested) return;
Source.Merge(result);
}
catch (Exception) { }
}
private void CreateFilter(List<long> id_list)
{
lock (Data)
{
Data.Clear();
Data.Append("(0");
foreach (var value in id_list)
Data.Append("," + value);
Data.Append(")");
}
}
public string Filter
{
get
{
return Data.ToString();
}
}
}
`
Make Query store information if it should use it's own filter (you said that you want to use it's filter only some times, in other cases you want to use Downloader.Filter)
public class Query
{
DataTable Source;
Func<string, DataTable> Download;
StringBuilder Data;
public bool IsOwnFilter { get; set; }
public Query(DataTable Source, Func<string, DataTable> Download, bool isOwnFilter = false)
{
this.Source = Source;
this.Data = new StringBuilder();
this.Download = Download;
this.IsOwnFilter = isOwnFilter;
}
public async Task Execute(strign filter, CancellationToken cancellationToken)
{
try
{
DataTable result = await Task.Run(() => Download(filter));
if (cancellationToken.IsCancellationRequested) return;
Source.Merge(result);
}
catch (Exception) { }
}
// This is not used in your code
private void CreateFilter(List<long> id_list)
{
lock (Data)
{
Data.Clear();
Data.Append("(0");
foreach (var value in id_list)
Data.Append("," + value);
Data.Append(")");
}
}
public string Filter
{
get
{
return Data.ToString();
}
}
}
Next when you call it from Downloader, check for new flag and pass correct filter:
private Task Execute()
{
var Tasks = new List<Task>(Queries.Count);
foreach (var query in Queries)
{
// call Execute with Query filter or Downloader.Filter
var task = query.Execute(query.IsOwnFilter ? query.Filter : Filter, CancellationToken);
Tasks.Add(task);
}
return Task.WhenAll(Tasks);
}
And when you add queries to Downloader:
Downloader downloader = new Downloader();
downloader.Add(new List<Query>(){
new Query(dataset.trans_sprzedaz_pozycje, (filter) => dataset.PobierzTransSprzedazPozycja(filter)),
new Query(dataset.trans_sprzedaz_rabaty_dodatkowe, (filter) => dataset.PobierzDokumentySprzedazyRabaty(filter)),
new Query(dataset.trans_sprzedaz_teksty, (filter) => dataset.PobierzDokumentySprzedazyTeksty(filter))
});
I am using Xamarin to develop an Apple Watch app. I am trying to send a message from my watch to the iPhone with my SendMessage function. When I do this, I get in the out error the message payload could not be delivered. I can only read part of the message ("payload could n...") because I am writing it on a label (since my debugger doesn't work in Xamarin I can't take a look at the message), but after doing some googling I'm assuming that's what it is written. Any idea why? Here is my code:
public sealed class WCSessionManager : NSObject, IWCSessionDelegate
{
// Setup is converted from https://www.natashatherobot.com/watchconnectivity-say-hello-to-wcsession/
// with some extra bits
private static readonly WCSessionManager sharedManager = new WCSessionManager();
private static WCSession session = WCSession.IsSupported ? WCSession.DefaultSession : null;
#if __IOS__
public static string Device = "Phone";
#else
public static string Device = "Watch";
#endif
public event ApplicationContextUpdatedHandler ApplicationContextUpdated;
public delegate void ApplicationContextUpdatedHandler(WCSession session, Dictionary<string, object> applicationContext);
public event MessageReceivedHandler MessageReceived;
public delegate void MessageReceivedHandler(Dictionary<string, object> message, Action<Dictionary<string, object>> replyHandler);
private WCSession validSession
{
get
{
#if __IOS__
// Even though session.Paired and session.WatchAppInstalled are underlined, it will still build as they are available on the iOS version of WatchConnectivity.WCSession
Console.WriteLine($"Paired status:{(session.Paired ? '✓' : '✗')}\n");
Console.WriteLine($"Watch App Installed status:{(session.WatchAppInstalled ? '✓' : '✗')}\n");
return (session.Paired && session.WatchAppInstalled) ? session : null;
//return session;
#else
return session;
#endif
}
}
private WCSession validReachableSession
{
get
{
return session.Reachable ? validSession : null;
}
}
private WCSessionManager() : base() { }
public static WCSessionManager SharedManager
{
get
{
return sharedManager;
}
}
public void StartSession()
{
if (session != null)
{
session.Delegate = this;
session.ActivateSession();
Console.WriteLine($"Started Watch Connectivity Session on {Device}");
}
}
[Export("sessionReachabilityDidChange:")]
public void SessionReachabilityDidChange(WCSession session)
{
Console.WriteLine($"Watch connectivity Reachable:{(session.Reachable ? '✓' : '✗')} from {Device}");
// handle session reachability change
if (session.Reachable)
{
// great! continue on with Interactive Messaging
}
else {
// 😥 prompt the user to unlock their iOS device
}
}
#region Application Context Methods
public void UpdateApplicationContext(Dictionary<string, object> applicationContext)
{
// Application context doesnt need the watch to be reachable, it will be received when opened
if (validSession != null)
{
try
{
var NSValues = applicationContext.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = applicationContext.Keys.Select(x => new NSString(x)).ToArray();
var NSApplicationContext = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
UpdateApplicationContextOnSession(NSApplicationContext);
}
catch (Exception ex)
{
Console.WriteLine($"Exception Updating Application Context: {ex.Message}");
}
}
}
public void GetApplicationContext()
{
UpdateApplicationContext(new Dictionary<string, object>() { { "GET", null } });
}
[Export("session:didReceiveApplicationContext:")]
public void DidReceiveApplicationContext(WCSession session, NSDictionary<NSString, NSObject> applicationContext)
{
Console.WriteLine($"Recieving Message on {Device}");
if (ApplicationContextUpdated != null)
{
var keys = applicationContext.Keys.Select(k => k.ToString()).ToArray();
IEnumerable<object> values;
try
{
values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString(), typeof(DoorWatchDTO)));
}
catch (Exception)
{
values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString()));
}
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
ApplicationContextUpdated(session, dictionary);
}
}
[Export("session:didReceiveMessage::")]
public void DidReceiveMessage(WCSession session, NSDictionary<NSString, NSObject> message, WCSessionReplyHandler replyHandler)
{
if (MessageReceived != null)
{
var keys = message.Keys.Select(k => k.ToString()).ToArray();
IEnumerable<object> values;
values = message.Values.Select(v => JsonConvert.DeserializeObject(v.ToString()));
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
MessageReceived(dictionary, (dict) =>
{
var NSValues = dict.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = dict.Keys.Select(x => new NSString(x)).ToArray();
var NSDict = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
replyHandler.Invoke(NSDict);
});
}
}
public void SendMessage(Dictionary<string, object> message, Action<Dictionary<string, object>> replyHandler, WKInterfaceLabel label)
{
if (validSession != null)
{
try
{
var NSValues = message.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = message.Keys.Select(x => new NSString(x)).ToArray();
var NSMessage = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
var reply = new WCSessionReplyHandler((replyMessage) =>
{
var values = replyMessage.Values.Select(x => JsonConvert.SerializeObject(x)).ToArray();
var keys = replyMessage.Keys.Select(x => x.ToString()).ToArray();
var dict = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => (object)x.Value);
replyHandler.Invoke(dict);
});
validSession.SendMessage(NSMessage, reply, (error) =>
{
label.SetText(error.ToString()); // I can see the error in here: "payload could n..."
});
}
catch (Exception ex)
{
Console.WriteLine($"Exception sending message: {ex.Message}");
}
}
}
private void UpdateApplicationContextOnSession(NSDictionary<NSString, NSObject> NSApplicationContext)
{
NSError error;
var sendSuccessfully = validSession.UpdateApplicationContext(NSApplicationContext, out error);
if (sendSuccessfully)
{
Console.WriteLine($"Sent App Context from {Device} \nPayLoad: {NSApplicationContext.ToString()} \n");
#if __IOS__
Logging.Log("Success, payload: " + NSApplicationContext.ToString());
#endif
}
else
{
Console.WriteLine($"Error Updating Application Context: {error.LocalizedDescription}");
#if __IOS__
Logging.Log("error: " + error.LocalizedDescription);
#endif
}
}
#endregion
I solved it. Instead of implementing IWCSessionDelegate, I simply implement WCSessionDelegate instead and override the functions as needed.
I have a List and this SomeAttachmentClass has an UploadCompleted event that is, upon calling a .Save() method, raised exactly once. That .Save() method does not block until the save and therefore upload is actually completed, but returns right away and signals its completion via said event.
Now what I want and need to do is basically wait for all the instances in the List<> to raise that event (exactly) once and only then continue.. & the question is - how would I do that most simply/elegantly?
I was thinking of using RX's FromEvent(..) in combination with .Take(1) on every instance, perform a .SelectMany() over all these per-instance streams and await that.. but I am not sure whether there's a better way out there.
This works to solve the problem - thanks to Shlomo for the starting point.
void Main()
{
Func<SomeAttachment, IObservable<object>> save = sa =>
Observable.Create<object>(o =>
{
var ob =
Observable
.FromEventPattern<object>(
eh => sa.UploadCompleted += eh,
eh => sa.UploadCompleted -= eh)
.Take(1)
.Select(x => x.EventArgs);
var subscription = ob.Subscribe(o);
sa.Save();
return subscription;
});
var list = Enumerable.Range(0, 10).Select(i => new SomeAttachment(i)).ToList();
list
.ToObservable()
.SelectMany(o => save(o))
.ToArray()
.Subscribe(_ => Console.WriteLine("All Complete. Handling logic goes here."));
}
public class SomeAttachment
{
private static Random random = new Random();
private readonly int _id;
public SomeAttachment(int id)
{
_id = id;
}
public int Id
{
get { return _id; }
}
public async Task Save()
{
//await Task.Delay(TimeSpan.FromMilliseconds(random.Next(1000, 3000)));
UploadCompleted?.Invoke(this, new object());
}
public event EventHandler<object> UploadCompleted;
}
Here's an Rx implementation. You don't need to await, Rx will do that for you. Commented out lines are for debugging/running in LinqPad:
void Main()
{
var list = Enumerable.Range(0, 10).Select(i => new SomeAttachment(i)).ToList();
list.ToObservable()
.Do(sa => sa.Save())
//.Do(sa => Console.WriteLine($"{sa.Id}: Save called"))
.Select(sa => Observable.FromEventPattern<object>(eh => sa.UploadCompleted += eh, eh => sa.UploadCompleted -= eh))
.SelectMany(o => o.Take(1))
//.Do(o => Console.WriteLine($"{(o.Sender as SomeAttachment).Id}: Upload completed."))
.All(_ => true)
.Take(1)
.Subscribe(_ => Console.WriteLine("All Complete. Handling logic goes here."));
}
// Define other methods and classes here
public class SomeAttachment
{
private static Random random = new Random();
private readonly int _id;
public SomeAttachment(int id)
{
_id = id;
}
public int Id
{
get { return _id; }
}
public async Task Save()
{
await Task.Delay(TimeSpan.FromMilliseconds(random.Next(1000, 3000)));
UploadCompleted?.Invoke(this, new object());
}
public event EventHandler<object> UploadCompleted;
}
I am unable to use AsanaNet from C#. When instantiating a new Asana instance, i am unable to pass errorCallback. The error I get is "The errorCallback does not exist in current context". Below is the my code.
class Program
{
private const string _apiKey = "API";
private static Asana _asana;
static void Main(string[] args)
{
Console.WriteLine("Step 1");
_asana = new Asana(_apiKey, AuthenticationType.Basic, errorCallback);
var user = new AsanaUser();
_asana.GetMe(o =>
{
user = o as AsanaUser;
});
Console.WriteLine("Step 2");
_asana.GetWorkspaces(o =>
{
foreach (AsanaWorkspace workspace in o)
{
Console.WriteLine("Workspace Name={0}", workspace.Name);
}
});
Console.WriteLine("Step 3");
_asana.GetWorkspaces(o =>
{
foreach (AsanaWorkspace workspace in o)
{
_asana.GetProjectsInWorkspace(workspace, projects =>
{
foreach (AsanaProject project in projects)
{
Console.WriteLine("Project Name=" + project.Name);
}
}
);
}
});
}
}
According to https://github.com/acron0/AsanaNet/blob/master/AsanaNet/Asana.cs, the constructor has this signature:
public Asana(string apiKeyOrBearerToken, AuthenticationType authType, Action<string, string, string> errorCallback)
So you can declare your error callback method like this:
static void errorCallback(string s1, string s2, string s3)
{
}
Also, if you don't want handling anything, you can just pass an empty lambda into constructor:
_asana = new Asana(_apiKey, AuthenticationType.Basic, (s1, s2, s3) => {});