Using Stateless library for .NET I wrote a state machine that does a few things:
Read a barcode ticket
do some validation
check for some more validation
do some digital I/O(turn a light green and open a gate)
frame a transaction and commit it
My StateMachine code is as follows:
public class MainStateMachine
{
public delegate void tvOnTransitioned(StateMachine<State, Trigger>.Transition otransition);
public delegate void tvUnhandledTrigger(State estate, Trigger etrigger);
public delegate void tvEntryExit();
public delegate bool bnGuardClause();
public delegate void vLogMsg(string sstring);
public delegate void tvOnTransition_Barcode(sbarcodeserial);
StateMachine<State, Trigger>.TriggerWithParameters<string> oBarcode_Trigger_With_Parameters;
public tvOnTransitioned OnTransitioned = null;
public tvUnhandledTrigger OnUnhandledTrigger = null;
#region SuperState
public tvEntryExit OnEntry_Input = null;
public tvEntryExit OnExit_Input = null;
#endregion
#region Substates_Entry_Exit
public tvEntryExit OnEntry_CSC_BarcodeValidation = null;
public tvEntryExit OnEntry_PaymentSatisfied = null;
public tvEntryExit OnEntry_FrameTransaction = null;
#endregion
#region Transitions
public tvEntryExit OnTransition_BarcodeValid = null;
public tvEntryExit OnTransition_OCR_Validation_OK = null;
public tvEntryExit OnTransition_LD_RisingEdge = null;
public tvEntryExit OnTransition_BarcodeError = null;
public tvEntryExit OnTransition_BarcodeInvalid = null;
public tvEntryExit OnTransition_OCR_ValidationNot_OK = null;
#endregion
public tvOnTransition_Barcode OnTransition_DO_CSCBarcodeValidation = null;
public enum State
{
//Super states
Input,
//sub states
CSC_Barcode_Validation, OCR_BarcodeValidation, PaymentSatisfied, Transaction
}
public enum Trigger
{
OpenLane,
Trg_CloseLane,
Trg_BC,
Trg_BarCodeRead,
Trg_BarcodeError,
Trg_BarcodeInvalid,
Trg_BarcodeValid,
Trg_OCR_ValidationNot_OK,
Trg_OCR_Validation_OK,
Trg_LDRisingEdge,
Trg_LDFallingEdge
}
State _state = State.Input;
StateMachine<State, Trigger> _sMachine;
object oLock;
public MainStateMachine()
{
this.oLock = new object();
_sMachine = new StateMachine<State, Trigger>(() => _state, s => _state = s);
this._sMachine.OnTransitioned((otransition) => { if (this.OnTransitioned != null) this.OnTransitioned(otransition); });
this._sMachine.OnUnhandledTrigger((estate, etrigger) => { if (this.OnUnhandledTrigger != null) this.OnUnhandledTrigger(estate, etrigger); });
this.oBarcode_Trigger_With_Parameters = this._sMachine.SetTriggerParameters<string>(Trigger.Trg_BC);
//Tag/Barcode Input state
_sMachine.Configure(State.Input)
.Permit(Trigger.Trg_BC, State.CSC_Barcode_Validation)
.OnEntry(() => { if (this.OnEntry_Input != null) this.OnEntry_Input(); })
.OnExit(() => { if (this.OnExit_Input != null) this.OnExit_Input(); });
_sMachine.Configure(State.CSC_Barcode_Validation)
.SubstateOf(State.Input)
.OnEntryFrom(this.oBarcode_Trigger_With_Parameters, (sbarcodeserial) => { if (this.OnTransition_DO_CSCBarcodeValidation != null) this.OnTransition_DO_CSCBarcodeValidation( sbarcodeserial); })
.Permit(Trigger.Trg_BarcodeError, State.Transaction)
.Permit(Trigger.Trg_BarcodeValid, State.OCR_BarcodeValidation)
.OnEntry(() => { if (OnEntry_CSC_BarcodeValidation != null) OnEntry_CSC_BarcodeValidation(); });
_sMachine.Configure(State.OCR_BarcodeValidation)
.SubstateOf(State.Input)
.OnEntryFrom(Trigger.Trg_BarcodeValid, () =>
{
if(this.OnTransition_BarcodeValid != null) this.OnTransition_BarcodeValid();
})
.Permit(Trigger.Trg_OCR_ValidationNot_OK, State.Transaction)
.Permit(Trigger.Trg_OCR_Validation_OK, State.PaymentSatisfied);
_sMachine.Configure(State.PaymentSatisfied)
.Permit(Trigger.Trg_LDRisingEdge, State.Transaction)
.OnEntryFrom(Trigger.Trg_OCR_Validation_OK, () =>
{
if (this.OnTransition_OCR_Validation_OK != null) this.OnTransition_OCR_Validation_OK();
})
.OnEntry(() => { if (this.OnEntry_PaymentSatisfied != null) OnEntry_PaymentSatisfied(); }); //.OnExit(() => { if (this.OnExit_PaymentSatisfied != null) OnExit_PaymentSatisfied(); });
_sMachine.Configure(State.Transaction)
.OnEntryFrom(Trigger.Trg_LDRisingEdge, () =>
{
if (this.OnTransition_LD_RisingEdge != null) this.OnTransition_LD_RisingEdge();
})
.OnEntryFrom(Trigger.Trg_BarcodeError, () => { if (this.OnTransition_BarcodeError != null) this.OnTransition_BarcodeError(); })
.OnEntryFrom(Trigger.Trg_BarcodeInvalid, () => { if (this.OnTransition_BarcodeInvalid != null) this.OnTransition_BarcodeInvalid(); })
.OnEntryFrom(Trigger.Trg_OCR_ValidationNot_OK, () => { if (this.OnTransition_OCR_ValidationNot_OK != null) this.OnTransition_OCR_ValidationNot_OK(); })
.OnEntry(() => { if (this.OnEntry_FrameTransaction != null) OnEntry_FrameTransaction(); }); // on entry into this State, I have to frame a Transaction and commit it
}
public bool TryFireTrigger(Trigger etrigger)
{
bool bReturn = false;
lock (this.oLock)
{
if (this._sMachine.CanFire(etrigger))
{
try
{
this._sMachine.Fire(etrigger);
bReturn = true;
}
catch (Exception oException)
{
bReturn = false;
Console.WriteLine(oException.StackTrace);
}
}
}
return bReturn;
}
}
This State machine is for a vehicle when it enters an unattended booth in a parking lot. I can't figure out as to how to create a transaction object, because it's properties change as and how it undergoes a state change.
Do I pass the Transaction object to each state and get it's modified state back and pass it to next state? I'm not sure how to go about it. Any help is appreciated.
Edit: Another question. What is a substate?
Substates inherit the allowed transitions of their superstate. When entering directly into a substate from outside of the superstate, entry actions for the superstate are executed. Likewise when leaving from the substate to outside the superstate, exit actions for the superstate will execute.
Related
I have a somewhat unusual problem.
I wrote a damage meter for a game and have the problem, if I attack 2 opponents at the same time in the game, I am shown several times in the list.
To get the data from the game, I use PhotonParser, which reads out the network data.
(First of all, it's nothing illegal.)
public class AlbionPackageParser : PhotonParser
{
private readonly HealthUpdateEventHandler HealthUpdateEventHandler;
public AlbionPackageParser(TrackingController trackingController, MainWindowViewModel mainWindowViewModel)
{
HealthUpdateEventHandler = new HealthUpdateEventHandler(trackingController);
}
protected override void OnEvent(byte code, Dictionary<byte, object> parameters)
{
var eventCode = ParseEventCode(parameters);
if (eventCode == EventCodes.Unused)
{
return;
}
Task.Run(async () =>
{
switch (eventCode)
{
case EventCodes.HealthUpdate:
await HealthUpdateEventHandlerAsync(parameters).ConfigureAwait(false);
return;
}
});
}
private static EventCodes ParseEventCode(IReadOnlyDictionary<byte, object> parameters)
{
if (!parameters.TryGetValue(252, out var value))
{
return EventCodes.Unused;
}
return (EventCodes)Enum.ToObject(typeof(EventCodes), value);
}
private async Task HealthUpdateEventHandlerAsync(Dictionary<byte, object> parameters)
{
var value = new HealthUpdateEvent(parameters);
await HealthUpdateEventHandler.OnActionAsync(value);
}
}
public class HealthUpdateEventHandler
{
private readonly TrackingController _trackingController;
public HealthUpdateEventHandler(TrackingController trackingController)
{
_trackingController = trackingController;
}
public async Task OnActionAsync(HealthUpdateEvent value)
{
await _trackingController.CombatController.AddDamageAsync(value.ObjectId, value.CauserId, value.HealthChange, value.NewHealthValue);
}
}
public class HealthUpdateEvent
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod()?.DeclaringType);
public long CauserId;
public int CausingSpellType;
public EffectOrigin EffectOrigin;
public EffectType EffectType;
public double HealthChange;
public double NewHealthValue;
public long ObjectId;
public GameTimeStamp TimeStamp;
public HealthUpdateEvent(Dictionary<byte, object> parameters)
{
ConsoleManager.WriteLineForNetworkHandler(GetType().Name, parameters);
try
{
if (parameters.ContainsKey(0))
{
ObjectId = parameters[0].ObjectToLong() ?? throw new ArgumentNullException();
}
if (parameters.ContainsKey(1))
{
TimeStamp = new GameTimeStamp(parameters[1].ObjectToLong() ?? 0);
}
if (parameters.ContainsKey(2))
{
HealthChange = parameters[2].ObjectToDouble();
}
if (parameters.ContainsKey(3))
{
NewHealthValue = parameters[3].ObjectToDouble();
}
if (parameters.ContainsKey(4))
{
EffectType = (EffectType) (parameters[4] as byte? ?? 0);
}
if (parameters.ContainsKey(5))
{
EffectOrigin = (EffectOrigin) (parameters[5] as byte? ?? 0);
}
if (parameters.ContainsKey(6))
{
CauserId = parameters[6].ObjectToLong() ?? throw new ArgumentNullException();
}
if (parameters.ContainsKey(7))
{
CausingSpellType = parameters[7].ObjectToShort();
}
}
catch (ArgumentNullException ex)
{
ConsoleManager.WriteLineForError(MethodBase.GetCurrentMethod()?.DeclaringType, ex);
Log.Error(MethodBase.GetCurrentMethod()?.DeclaringType, ex);
}
catch (Exception e)
{
ConsoleManager.WriteLineForError(MethodBase.GetCurrentMethod()?.DeclaringType, e);
Log.Error(MethodBase.GetCurrentMethod()?.DeclaringType, e);
}
}
}
Most important file:
public class CombatController
{
public async Task AddDamageAsync(long objectId, long causerId, double healthChange, double newHealthValue)
{
if (!IsDamageMeterActive || objectId == causerId)
{
return;
}
var gameObject = _trackingController?.EntityController?.GetEntity(causerId);
if (gameObject?.Value == null
|| gameObject.Value.Value?.ObjectType != GameObjectType.Player
|| !_trackingController.EntityController.IsUserInParty(gameObject.Value.Value.Name)
)
{
return;
}
if (GetHealthChangeType(healthChange) == HealthChangeType.Damage)
{
var damageChangeValue = (int)Math.Round(healthChange.ToPositiveFromNegativeOrZero(), MidpointRounding.AwayFromZero);
if (damageChangeValue <= 0)
{
return;
}
gameObject.Value.Value.Damage += damageChangeValue;
}
if (GetHealthChangeType(healthChange) == HealthChangeType.Heal)
{
var healChangeValue = healthChange;
if (healChangeValue <= 0)
{
return;
}
if (IsMaxHealthReached(objectId, newHealthValue))
{
return;
}
gameObject.Value.Value.Heal += (int)Math.Round(healChangeValue, MidpointRounding.AwayFromZero);
}
if (gameObject.Value.Value?.CombatStart == null)
{
gameObject.Value.Value.CombatStart = DateTime.UtcNow;
}
if (await IsUiUpdateAllowedAsync() && !IsUiUpdateActive)
{
await UpdateDamageMeterUiAsync(_mainWindowViewModel.DamageMeter, _trackingController.EntityController.GetAllEntities());
}
}
private static bool IsUiUpdateActive;
public async Task UpdateDamageMeterUiAsync(AsyncObservableCollection<DamageMeterFragment> damageMeter, List<KeyValuePair<Guid, PlayerGameObject>> entities)
{
IsUiUpdateActive = true;
var highestDamage = entities.GetHighestDamage();
var highestHeal = entities.GetHighestHeal();
_trackingController.EntityController.DetectUsedWeapon();
foreach (var healthChangeObject in entities)
{
if (_mainWindow?.Dispatcher == null || healthChangeObject.Value?.UserGuid == null)
{
continue;
}
var fragment = damageMeter.ToList().FirstOrDefault(x => x.CauserGuid == healthChangeObject.Value.UserGuid);
if (fragment != null)
{
await UpdateDamageMeterFragmentAsync(fragment, healthChangeObject, entities, highestDamage, highestHeal).ConfigureAwait(true);
}
else
{
await AddDamageMeterFragmentAsync(damageMeter, healthChangeObject, entities, highestDamage, highestHeal).ConfigureAwait(true);
}
Application.Current.Dispatcher.Invoke(() => _mainWindowViewModel.SetDamageMeterSort());
}
if (HasDamageMeterDupes(_mainWindowViewModel.DamageMeter))
{
await RemoveDuplicatesAsync(_mainWindowViewModel.DamageMeter);
}
IsUiUpdateActive = false;
}
private async Task UpdateDamageMeterFragmentAsync(DamageMeterFragment fragment, KeyValuePair<Guid, PlayerGameObject> healthChangeObject, List<KeyValuePair<Guid, PlayerGameObject>> entities, long highestDamage, long highestHeal)
{
var itemInfo = await SetItemInfoIfSlotTypeMainHandAsync(fragment.CauserMainHand, healthChangeObject.Value?.CharacterEquipment?.MainHand).ConfigureAwait(false);
fragment.CauserMainHand = itemInfo;
// Damage
if (healthChangeObject.Value?.Damage > 0)
{
fragment.DamageInPercent = (double)healthChangeObject.Value.Damage / highestDamage * 100;
fragment.Damage = (long)healthChangeObject.Value?.Damage;
}
if (healthChangeObject.Value?.Dps != null)
{
fragment.Dps = healthChangeObject.Value.Dps;
}
// Heal
if (healthChangeObject.Value?.Heal > 0)
{
fragment.HealInPercent = (double)healthChangeObject.Value.Heal / highestHeal * 100;
fragment.Heal = (long)healthChangeObject.Value?.Heal;
}
if (healthChangeObject.Value?.Hps != null)
{
fragment.Hps = healthChangeObject.Value.Hps;
}
// Generally
if (healthChangeObject.Value != null)
{
fragment.DamagePercentage = entities.GetDamagePercentage(healthChangeObject.Value.Damage);
fragment.HealPercentage = entities.GetHealPercentage(healthChangeObject.Value.Heal);
_trackingController.EntityController.SetPartyCircleColor(healthChangeObject.Value.UserGuid, itemInfo?.FullItemInformation?.CategoryId);
}
}
private async Task AddDamageMeterFragmentAsync(AsyncObservableCollection<DamageMeterFragment> damageMeter, KeyValuePair<Guid, PlayerGameObject> healthChangeObject,
List<KeyValuePair<Guid, PlayerGameObject>> entities, long highestDamage, long highestHeal)
{
if (healthChangeObject.Value == null
|| (double.IsNaN(healthChangeObject.Value.Damage) && double.IsNaN(healthChangeObject.Value.Heal))
|| (healthChangeObject.Value.Damage <= 0 && healthChangeObject.Value.Heal <= 0))
{
return;
}
var mainHandItem = ItemController.GetItemByIndex(healthChangeObject.Value?.CharacterEquipment?.MainHand ?? 0);
var itemInfo = await SetItemInfoIfSlotTypeMainHandAsync(mainHandItem, healthChangeObject.Value?.CharacterEquipment?.MainHand);
var damageMeterFragment = new DamageMeterFragment
{
CauserGuid = healthChangeObject.Value.UserGuid,
Damage = healthChangeObject.Value.Damage,
Dps = healthChangeObject.Value.Dps,
DamageInPercent = (double)healthChangeObject.Value.Damage / highestDamage * 100,
DamagePercentage = entities.GetDamagePercentage(healthChangeObject.Value.Damage),
Heal = healthChangeObject.Value.Heal,
Hps = healthChangeObject.Value.Hps,
HealInPercent = (double)healthChangeObject.Value.Heal / highestHeal * 100,
HealPercentage = entities.GetHealPercentage(healthChangeObject.Value.Heal),
Name = healthChangeObject.Value.Name,
CauserMainHand = itemInfo
};
damageMeter.Init(Application.Current.Dispatcher.Invoke);
damageMeter.Add(damageMeterFragment);
_trackingController.EntityController.SetPartyCircleColor(healthChangeObject.Value.UserGuid, itemInfo?.FullItemInformation?.CategoryId);
}
// Try 1
private bool IsUiUpdateAllowed(int waitTimeInSeconds = 1)
{
var currentDateTime = DateTime.UtcNow;
var difference = currentDateTime.Subtract(_lastDamageUiUpdate);
if (difference.Seconds >= waitTimeInSeconds && !IsUiUpdateActive)
{
_lastDamageUiUpdate = currentDateTime;
return true;
}
return false;
}
// Try 2
private async Task<bool> IsUiUpdateAllowedAsync()
{
await Task.Delay(100).ConfigureAwait(true);
if (_updateReadyCounter++ < 2)
{
return false;
}
_updateReadyCounter = 0;
return true;
}
}
In the last class "CombatController" I am currently trying to get the problem under control.
The "UpdateDamageMeterUiAsync ()" method should only be triggered once if IsUiUpdateAllowedAsync () is true.
Nevertheless, "UpdateDamageMeterUiAsync ()" is always triggered 2-3 times at the same time, which is why multiple entries are created. All other lists are fine, only the UI object "AsyncObservableCollection _mainWindowViewModel.DamageMeter" has the problem.
Does anyone have any idea how I can tackle the problem?
I am using #aspnet/signalr to connect to a hub and from the hub i am calling the method to return me some data.
When i first initialize the server the connection is established normally, but if i refresh the window 3 or 4 times the client stop sending connection request to the server.
I tried logging the HubConnection, the connectionState at first is equal to 1 but after the problem accrues the connectionState is alwase equal 0
Here is how i am building the hubConnection:
buildConnection() {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.tradesService.getStockQuotationsHubUrl())
.build();
this.hubConnection.serverTimeoutInMilliseconds = 1000 * 10;
this.hubConnection.onclose(() => setTimeout(() => this.startSignalRConnection(), 2000));}
Here is how i am starting the hubConnection:
startConnection() {
this.hubConnection
.start()
.then(() => {
this.hubConnection.on('updateMethod', (data: any) => {
this.store.push([
{ type: 'update', key: data.stockID, data },
]);
});
this.dataSource = new DataSource({
store: this.store,
reshapeOnPush: true,
});
});}
Note: I am listing to 5 hubs at once in my page but the only one having problems is this one.
[Update]
The problem is from the server because when i restart the server the connection is reestablished between the client and the server, but if the client refresh or quit the page for multiple times the hub does not even try to connect to the client
public class StockQuotationsHub : Microsoft.AspNetCore.SignalR.Hub
{
private string SectorID { get; set; }
private int TradingSession { get; set; }
private int MarketID { get; set; }
public static StockQuotationExt stockQuotationExt { get; set; }
private readonly StockQuotationTicker _stockTicker;
public StockQuotationsHub(StockQuotationTicker stockTicker){
_stockTicker = stockTicker;
}
public override Task OnConnectedAsync(){
return base.OnConnectedAsync();
}
public IEnumerable<StockQuotation> GetAllStockQuotations(string[] stockID, string sectorID, int tradingSession, int marketType){
return _stockTicker.
GetAllStocks(Context.ConnectionId, stockID, sectorID, tradingSession, marketType);
}
public override async Task OnDisconnectedAsync(Exception exception){
await base.OnDisconnectedAsync(exception);
}
and here is my stock ticker class:
public IEnumerable<StockQuotation> GetAllStocks(string connectionId, string[] stockID, string sectorID, int tradingSession, int marketType)
{
_stocks = new List<StockQuotation>();
_stocks = Task.Run(async () => await GetStockQuotationModelAsync("", 0, 1, 0)).Result.ToList();
this.MaxTimeStamp = _stocks.Max(s => s.TStamp);
this.SectorID = sectorID;
this.TradingSession = tradingSession;
this.MarketID = marketType;
AddToGroups(connectionId, stockID);
if (_timer==null)
_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
if (stockID.Length == 0)
{
return _stocks;
}
else
{
var stocksList = new List<StockQuotation>();
foreach (var stock in stockID)
{
stocksList.AddRange(_stocks.Where(s => s.StockID == stock).ToList());
}
return stocksList;
}
}
private void AddToGroups(string connectionId, string[] stockID)
{
if (_stocks.Count > 0)
{
if (stockID.Length == 0)
{
Hub.Groups.AddToGroupAsync(connectionId, "ALL");
}
else
{
foreach (var stock in stockID)
{
Hub.Groups.AddToGroupAsync(connectionId, stock);
var s = _stocks.FirstOrDefault(s => s.StockID == stock);
if(s != null)
{
s.Snapshots = new List<double>(GetStockQuotationSnapshots(stock));
}
}
}
}
}
private void AddToGroups(string connectionId, string[] stockID)
{
if (_stocks.Count > 0)
{
if (stockID.Length == 0)
{
Hub.Groups.AddToGroupAsync(connectionId, "ALL");
}
else
{
foreach (var stock in stockID)
{
Hub.Groups.AddToGroupAsync(connectionId, stock);
var s = _stocks.FirstOrDefault(s => s.StockID == stock);
if(s != null)
{
s.Snapshots = new List<double>(GetStockQuotationSnapshots(stock));
}
}
}
}
}
I really appreciated the help.
Eventually the problem was that the project type was MVC, so I changed it to be webApi
and everything worked successfully
Even if you fix it I would like to recommend you to add the following code in order to know exactly the root cause of disconnection.
First of all enable the extended debug info in the service configuration:
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
Furthermore, you need to attach to the event Closed in HubConnection like that:
hubConnection.Closed += (exception) =>
{
if (exception == null)
{
Console.WriteLine("Connection closed without error.");
}
else
{
Console.WriteLine($"Connection closed due to an error: {exception}");
}
return null;
};
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 am developing my first WPF browser application.
I load invoices in a dataGrid then I filter with textBox or comboBox.
Because it takes few seconds to load, I'am trying to put a loading animation according the following example:
here
I want to filter my dataGrid depending on two comboBox.
But I have this error
This type of CollectionView does not support changes to its
SourceCollection from a thread different from the Dispatcher thread
at the line invoiceCollection.Clear();
and invoiceCollection.Add(inv); in SearchFilter();
I tried
App.Current.Dispatcher.Invoke((Action)delegate // <--- HERE
{
//code here
});
but I still have the same error.
ViewModel
public class ConsultInvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
private ICollectionView _dataGridCollection;
private ObservableCollection<Invoice> invoiceCollection;
public ConsultInvoiceViewModel()
{
if (!WPFHelper.IsInDesignMode)
{
var tsk = Task.Factory.StartNew(InitialStart);
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
private void InitialStart()
{
try
{
State = StateEnum.Busy;
DataGridCollection = CollectionViewSource.GetDefaultView(Get());
DataGridCollection.Filter = new Predicate<object>(Filter);
}
finally
{
State = StateEnum.Idle;
}
}
private void SearchFilter()
{
Task tsk = Task.Factory.StartNew(()=>
{
try
{
State = StateEnum.Busy;
using (var ctx = new Context())
{
var invs = ctx.Invoices
.Where(s.supplier == 1)
.GroupBy(x => new { x.suppInvNumber, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
});
.
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Clear();
});
if (invs != null)
foreach (var inv in invs)
{
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Add(inv);
});
}
}
}
finally
{
State = StateEnum.Idle;
}
});
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
public static readonly PropertyChangedEventArgs StateArgs = ViewModelBase.CreateArgs<ConsultInvoiceViewModel>(c => c.State);
private StateEnum _State;
public StateEnum State
{
get
{
return _State;
}
set
{
var oldValue = State;
_State = value;
if (oldValue != value)
{
OnStateChanged(oldValue, value);
OnPropertyChanged(StateArgs);
}
}
}
protected virtual void OnStateChanged(StateEnum oldValue, StateEnum newValue)
{
}
}
ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region "INotifyPropertyChanged members"
public event PropertyChangedEventHandler PropertyChanged;
//This routine is called each time a property value has been set.
//This will //cause an event to notify WPF via data-binding that a change has occurred.
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null)
PropertyChanged(this, args);
}
public static PropertyChangedEventArgs CreateArgs<T>(Expression<Func<T, Object>> propertyExpression)
{
return new PropertyChangedEventArgs(GetNameFromLambda(propertyExpression));
}
private static string GetNameFromLambda<T>(Expression<Func<T, object>> propertyExpression)
{
var expr = propertyExpression as LambdaExpression;
MemberExpression member = expr.Body is UnaryExpression ? ((UnaryExpression)expr.Body).Operand as MemberExpression : expr.Body as MemberExpression;
var propertyInfo = member.Member as PropertyInfo;
return propertyInfo.Name;
}
}
There is a really nice way to solve this in .Net 4.5 and greater:
private object _lock = new object();
BindingOperations.EnableCollectionSynchronization("YourCollection", _lock);
So you don't need to use the Dispatcher everytime you want to manipulate your collection.
Here are some resources for further information:
http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
BindingOperations.EnableCollectionSynchronization mystery in WPF
https://msdn.microsoft.com/en-us/library/hh198845%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
Maybe try another example:
Xaml:
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding .}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Data();
}
}
public class Data
{
public ObservableCollection<int> Items { get; set; }
public Data()
{
Items = new ObservableCollection<int>();
Filters();
}
public void Filters()
{
Task.Factory.StartNew(DoWork);
}
public void DoWork()
{
int i = 0;
while (true)
{
System.Threading.Thread.Sleep(1000);
App.Current.Dispatcher.BeginInvoke(new Action(() => { Items.Add(++i); }));
}
}
}
}
I finally what it was working. It was pretty simple.
public class ConsultInvoiceViewModel : ViewModelBase
{
public Context ctx = new Context();
private ICollectionView _dataGridCollection;
private ObservableCollection<Invoice> invoiceCollection;
public ConsultInvoiceViewModel()
{
invoiceCollection = new ObservableCollection<Invoice>();
DataGridCollection = CollectionViewSource.GetDefaultView(Get());
if (!WPFHelper.IsInDesignMode)
{
var tsk = Task.Factory.StartNew(InitialStart);
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
private void InitialStart()
{
try
{
State = StateEnum.Busy;
Get();
}
finally
{
State = StateEnum.Idle;
}
}
private void SearchFilter()
{
Task tsk = Task.Factory.StartNew(()=>
{
try
{
State = StateEnum.Busy;
using (var ctx = new Context())
{
var invs = ctx.Invoices
.Where(s.supplier == 1)
.GroupBy(x => new { x.suppInvNumber, x.foodSupplier })
.ToList()
.Select(i => new Invoice
{
suppInvNumber = i.Key.suppInvNumber,
foodSupplier = i.Key.foodSupplier,
totalPrice = i.Sum(t => t.totalPrice),
});
.
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Clear();
if (invs != null)
foreach (var inv in invs)
{
invoiceCollection.Add(inv);
}
});
}
}
finally
{
State = StateEnum.Idle;
}
});
tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
private ObservableCollection<Invoice> Get()
{
using (var ctx = new Context())
{
var invs = ctx.Invoices
foreach (var inv in invs)
{
App.Current.Dispatcher.Invoke((Action)delegate
{
invoiceCollection.Add(inv);
});
}
}
I have a method, shown below, which calls a service.
How can I run this method through thread?
public List<AccessDetails> GetAccessListOfMirror(string mirrorId,string server)
{
List<AccessDetails> accessOfMirror = new List<AccessDetails>();
string loginUserId = SessionManager.Session.Current.LoggedInUserName;
string userPassword = SessionManager.Session.Current.Password;
using (Service1Client client = new Service1Client())
{
client.Open();
accessOfMirror = client.GetMirrorList1(mirrorId, server, null);
}
return accessOfMirror;
}
In C# 3.5 or 4.0 you can do this.
var task = Task.Factory.StartNew<List<AccessDetails>>(() => GetAccessListOfMirror(mirrorId,server))
.ContinueWith(tsk => ProcessResult(tsk));
private void ProcessResult(Task task)
{
var result = task.Result;
}
In C# 4.5 there's the await/async keywords which is some sugar for above
public async Task<List<AccessDetails>> GetAccessListOfMirror(string mirrorId,string server)
var myResult = await GetAccessListOfMirror(mirrorId, server)
Try something like this:
public async Task<List<AccessDetails>> GetAccessListOfMirror(string mirrorId, string server)
{
List<AccessDetails> accessOfMirror = new List<AccessDetails>();
string loginUserId = SessionManager.Session.Current.LoggedInUserName;
string userPassword = SessionManager.Session.Current.Password;
using (Service1Client client = new Service1Client())
{
client.Open();
Task<List<AccessDetails>> Detail = client.GetMirrorList1(mirrorId, server, null);
accessOfMirror = await Detail;
}
return accessOfMirror;
}
Below is a helper class I use, it references RX.NET.
If you include that in your project, then you can thread stuff very simply - the code above you could spin off to a separate thread as follows:
int mirrorId = 0;
string server = "xxx";
ASync.Run<List<AccessDetails>>(GetAccessListOfMirror(mirrorId,server), resultList => {
foreach(var accessDetail in resultList)
{
// do stuff with result
}
}, error => { // if error occured on other thread, handle exception here });
Worth noting: that lambda expression is merged back to the original calling thread - which is very handy if you're initiating your async operations from a GUI thread for example.
It also has another very handy method: Fork lets you spin off multiple worker threads and causes the calling thread to block until all the sub-threads are either complete or errored.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Concurrency;
namespace MyProject
{
public static class ASync
{
public static void ThrowAway(Action todo)
{
ThrowAway(todo, null);
}
public static void ThrowAway(Action todo, Action<Exception> onException)
{
if (todo == null)
return;
Run<bool>(() =>
{
todo();
return true;
}, null, onException);
}
public static bool Fork(Action<Exception> onError, params Action[] toDo)
{
bool errors = false;
var fork = Observable.ForkJoin(toDo.Select(t => Observable.Start(t).Materialize()));
foreach (var x in fork.First())
if (x.Kind == NotificationKind.OnError)
{
if(onError != null)
onError(x.Exception);
errors = true;
}
return !errors;
}
public static bool Fork<T>(Action<Exception> onError, IEnumerable<T> args, Action<T> perArg)
{
bool errors = false;
var fork = Observable.ForkJoin(args.Select(arg => Observable.Start(() => { perArg(arg); }).Materialize()));
foreach (var x in fork.First())
if (x.Kind == NotificationKind.OnError)
{
if (onError != null)
onError(x.Exception);
errors = true;
}
return !errors;
}
public static void Run<TResult>(Func<TResult> todo, Action<TResult> continuation, Action<Exception> onException)
{
bool errored = false;
IDisposable subscription = null;
var toCall = Observable.ToAsync<TResult>(todo);
var observable =
Observable.CreateWithDisposable<TResult>(o => toCall().Subscribe(o)).ObserveOn(Scheduler.Dispatcher).Catch(
(Exception err) =>
{
errored = true;
if (onException != null)
onException(err);
return Observable.Never<TResult>();
}).Finally(
() =>
{
if (subscription != null)
subscription.Dispose();
});
subscription = observable.Subscribe((TResult result) =>
{
if (!errored && continuation != null)
{
try
{
continuation(result);
}
catch (Exception e)
{
if (onException != null)
onException(e);
}
}
});
}
}
}