Dag,
Ik ben bezig met een applicatie die uit server / client combo bestaat. De server en client communiceren via websockets door het sturen van korte JSON berichtjes, welke ik serialize aan de ene kant en daarna deserialize aan de andere kant.
Deze berichten hebben een dynamic property 'Data' waar allerlei data in kan zitten afhankelijk van het doel van het bericht.
Ik merk al enige tijd vreemd gedrag in mijn applicatie wat ik niet kon verklaren en denk dat ik het nu heb terug gebracht tot een simpele test case. Het lijkt erop dat mijn test case zonder fouten verloopt in een niet-async uitvoering, maar "crasht" in een async uitvoering (dat wil zeggen: de debugger kapt er gewoon mee zonder fouten of niks, zelfs met try/catch er omheen). Soms krijg ik wel een foutmelding (zie later) in test cases, maar vaak ook niet, dit ligt misschien aan settings van welke exceptions gevangen worden ofzo...
Dit is mijn test case. Een class Message met dynamic property Data:
Een test object 'TestData' met twee properties die een data object moet voorstellen wat in de Data zou kunnen zitten:
Ik doe nu het volgende:
1. Maak een nieuwe Message.
2. Maak een nieuwe anonymous type met property 'Test' met een instance van TestData en ken die toe aan de Data van de message.
3. Serialize naar json met JSON.NET (Newtonsoft.Json versie 6.0.4).
4. Deserialize terug naar Message object.
Nu wil ik in het receivedMessage object wat ik 'terug krijg' (in werkelijkheid is dit dus naar een server verstuurt) het Data property ophalen en daar de TestData object uithalen. Dit TestData object gebruik ik verder in mijn applicatie, wat hier als een simpele method is gedaan waar (thread-safe) een label text gebruikt wordt:
Ik zie twee opties om de Data.Test te verkrijgen en naar DisplayTestData op te sturen:
1. Gebruik dynamic en roep gewoon receivedMessage.Data.Test aan:
Geen geklaag tijdens compile, maar tijdens runtime gaat dit mis met een Microsoft.CSharp.RuntimeBinder.RuntimeBinderException. Ik denk dat het probleem is dat 'receivedMessage.Data.Test' een JObject terug geeft en geen dynamic, en DisplayTestData accepteert geen JObject.
2. Ok, prima, dan anders: deserialize de test data gewoon vrolijk nog een keer en deserialize hem deze keer naar een TestData object:
Dit werkt prima! Geen geklaag, ook niet in runtime, en alles werkt naar behoren.
Probleem... Zodra ik met async methods ga werken loopt dit helemaal in de soep.
Als ik NIKS verander aan de code tot nu toe, behalve dat ik DisplayTestData async aanroep, dan crasht de boel:
Error: Cannot implicitly convert type 'void' to 'object'
Stack trace:
De oplossing is blijkbaar om het resultaat van DeserializeObject nog eens te casten naar TestData:
Met deze extra cast gaat het wel goed. Deze cast is dus niet nodig als ik DisplayTestData niet async aanroep..??
Waar ligt dit aan? En doe ik dit helemaal verkeerd of is dit de juiste manier om met dynamic data en json om te gaan?
Ik ben bezig met een applicatie die uit server / client combo bestaat. De server en client communiceren via websockets door het sturen van korte JSON berichtjes, welke ik serialize aan de ene kant en daarna deserialize aan de andere kant.
Deze berichten hebben een dynamic property 'Data' waar allerlei data in kan zitten afhankelijk van het doel van het bericht.
Ik merk al enige tijd vreemd gedrag in mijn applicatie wat ik niet kon verklaren en denk dat ik het nu heb terug gebracht tot een simpele test case. Het lijkt erop dat mijn test case zonder fouten verloopt in een niet-async uitvoering, maar "crasht" in een async uitvoering (dat wil zeggen: de debugger kapt er gewoon mee zonder fouten of niks, zelfs met try/catch er omheen). Soms krijg ik wel een foutmelding (zie later) in test cases, maar vaak ook niet, dit ligt misschien aan settings van welke exceptions gevangen worden ofzo...
Dit is mijn test case. Een class Message met dynamic property Data:
C#:
1
2
3
4
| public class Message { public dynamic Data { get; set; } } |
Een test object 'TestData' met twee properties die een data object moet voorstellen wat in de Data zou kunnen zitten:
C#:
1
2
3
4
5
| public class TestData { public string TestString { get; set; } public int TestInt { get; set; } } |
Ik doe nu het volgende:
1. Maak een nieuwe Message.
2. Maak een nieuwe anonymous type met property 'Test' met een instance van TestData en ken die toe aan de Data van de message.
3. Serialize naar json met JSON.NET (Newtonsoft.Json versie 6.0.4).
4. Deserialize terug naar Message object.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // Create message var sentMessage = new Message(); // Set data sentMessage.Data = new { Test = new TestData() { TestString = "abcd", TestInt = 42 } }; // Serialize to json (on client) var json = JsonConvert.SerializeObject(sentMessage); // Deserialize back to message (on server) var receivedMessage = JsonConvert.DeserializeObject<Message>(json); |
Nu wil ik in het receivedMessage object wat ik 'terug krijg' (in werkelijkheid is dit dus naar een server verstuurt) het Data property ophalen en daar de TestData object uithalen. Dit TestData object gebruik ik verder in mijn applicatie, wat hier als een simpele method is gedaan waar (thread-safe) een label text gebruikt wordt:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| private void DisplayTestData(TestData testData) { var testString = testData.TestString; this.SetText(testString); } private void SetText(string text) { // Set text (thread safe) if (label1.InvokeRequired) { label1.Invoke(new Action<string>(SetText), text); } else { label1.Text = text; } } |
Ik zie twee opties om de Data.Test te verkrijgen en naar DisplayTestData op te sturen:
1. Gebruik dynamic en roep gewoon receivedMessage.Data.Test aan:
C#:
1
2
3
4
| // Deserialize back to message (on server) var receivedMessage = JsonConvert.DeserializeObject<Message>(json); DisplayTestData(receivedMessage.Data.Test); |
Geen geklaag tijdens compile, maar tijdens runtime gaat dit mis met een Microsoft.CSharp.RuntimeBinder.RuntimeBinderException. Ik denk dat het probleem is dat 'receivedMessage.Data.Test' een JObject terug geeft en geen dynamic, en DisplayTestData accepteert geen JObject.
2. Ok, prima, dan anders: deserialize de test data gewoon vrolijk nog een keer en deserialize hem deze keer naar een TestData object:
C#:
1
2
3
4
5
6
7
| // Deserialize back to message (on server) var receivedMessage = JsonConvert.DeserializeObject<Message>(json); // Deserialize test data var testData = JsonConvert.DeserializeObject<TestData>(receivedMessage.Data.Test.ToString()); DisplayTestData(testData); |
Dit werkt prima! Geen geklaag, ook niet in runtime, en alles werkt naar behoren.
Probleem... Zodra ik met async methods ga werken loopt dit helemaal in de soep.
Als ik NIKS verander aan de code tot nu toe, behalve dat ik DisplayTestData async aanroep, dan crasht de boel:
C#:
1
2
3
4
| // Deserialize test data var testData = JsonConvert.DeserializeObject<TestData>(receivedMessage.Data.Test.ToString()); await Task.Run(() => DisplayTestData(testData)); |
Error: Cannot implicitly convert type 'void' to 'object'
Stack trace:
Dit gaat dus mis. Voor mijn gevoel exact dezelfde code, behalve dat ik de method async aanroep met Task.Run.at CallSite.Target(Closure , CallSite , Form1 , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at WindowsFormsApplication1.Form1.<>c__DisplayClass7.<RunAsyncTest>b__6() in f:\Users\Nick\Dropbox\VS Projects\DynamicJsonTest\WindowsFormsApplication1\Form1.cs:line 51
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at WindowsFormsApplication1.Form1.<RunAsyncTest>d__9.MoveNext() in f:\Users\Nick\Dropbox\VS Projects\DynamicJsonTest\WindowsFormsApplication1\Form1.cs:line 51
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__0(Object state)
De oplossing is blijkbaar om het resultaat van DeserializeObject nog eens te casten naar TestData:
C#:
1
2
3
4
| // Deserialize test data var testData = (TestData) JsonConvert.DeserializeObject<TestData>(receivedMessage.Data.Test.ToString()); await Task.Run(() => DisplayTestData(testData)); |
Met deze extra cast gaat het wel goed. Deze cast is dus niet nodig als ik DisplayTestData niet async aanroep..??
Waar ligt dit aan? En doe ik dit helemaal verkeerd of is dit de juiste manier om met dynamic data en json om te gaan?