My use case:
In a single threaded application, I need to serialize arbitrary classes for logging purposes.
The arbitrary classes are predominantly translated in an automated way from a massive VB6 application into .NET.
If serialized without a timeout, the serialization method will loop until it runs out of memory.
This is what I have currently:
internal class Serializer
{
private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public volatile string result = null;
public volatile Func<string> toExecute = null;
public Thread thread;
public ManualResetEventSlim messageToSender = new ManualResetEventSlim(false);
public ManualResetEventSlim messageToReceiver = new ManualResetEventSlim(false);
public Serializer()
{
thread = new Thread(new ThreadStart(run));
thread.Start();
}
~Serializer()
{
try
{
if (messageToSender != null) messageToSender.Dispose();
}
catch { };
try
{
if (messageToReceiver != null) messageToReceiver.Dispose();
}
catch { };
}
public volatile bool ending = false;
public void run()
{
while (!ending)
{
try
{
if (toExecute != null)
{
result = toExecute();
}
messageToReceiver.Reset();
messageToSender.Set();
messageToReceiver.Wait();
}
catch (ThreadInterruptedException)
{
log.Warn("Serialization interrupted");
break;
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
result = null;
}
catch (Exception ex)
{
log.Error("Error in Serialization", ex);
Console.WriteLine(ex);
break;
}
}
}
}
public class LocalStructuredLogging
{
private static volatile Serializer _serializer;
private static Serializer serializer
{
get
{
if (_serializer == null)
{
_serializer = new Serializer();
}
return _serializer;
}
}
public void LogStucturedEnd()
{
try
{
if (serializer != null)
{
serializer.ending = true;
serializer.thread.Interrupt();
}
}
catch { }
}
internal ConcurrentDictionary<long, bool> disallowedToSerialize = new ConcurrentDictionary<long, bool>();
public string TrySerialize<T>(T payload, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
bool dummy;
unchecked
{
if (disallowedToSerialize.TryGetValue(hashEl, out dummy))
{
return "°,°";
}
}
serializer.toExecute = () =>
{
try
{
return Newtonsoft.Json.JsonConvert.SerializeObject(payload, new Newtonsoft.Json.JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°°°";
}
};
try
{
serializer.messageToSender.Reset();
serializer.messageToReceiver.Set();
if (serializer.messageToSender.Wait(6000))
{
return Interlocked.Exchange(ref serializer.result, null);
}
serializer.toExecute = null;
serializer.thread.Abort();
serializer.messageToSender.Wait(2000);
disallowedToSerialize.TryAdd(hashEl, false);
return "°§°";
}
catch (Exception)
{
disallowedToSerialize.TryAdd(hashEl, false);
return "°-°";
}
}
}
The code is called as in the following (test is an arbitrary class instance):
var logger = new LocalStructuredLogging();
var rr5 = logger.TrySerialize(test);
Although it seems to do the job, there are some issues with it:
it has a dependency on Thread.Abort
it is time dependent, so it will thus produce varied results on a loaded system
every class instance is treated like every other class instance - no tweaking
...
So, are there any better solutions available ?
Based upon dbc's excellent answer, I managed to create a better timed serializer.
It resolves all 3 issues mentioned above:
public class TimedJsonTextWriter : JsonTextWriter
{
public int? MaxDepth { get; set; }
public TimeSpan? MaxTimeUsed { get; set; }
public int MaxObservedDepth { get; private set; }
private DateTime start = DateTime.Now;
public TimedJsonTextWriter(TextWriter writer, JsonSerializerSettings settings, TimeSpan? maxTimeUsed)
: base(writer)
{
this.MaxDepth = (settings == null ? null : settings.MaxDepth);
this.MaxObservedDepth = 0;
this.MaxTimeUsed = maxTimeUsed;
}
public TimedJsonTextWriter(TextWriter writer, TimeSpan? maxTimeUsed, int? maxDepth = null)
: base(writer)
{
this.MaxDepth = maxDepth;
this.MaxTimeUsed = maxTimeUsed;
}
public override void WriteStartArray()
{
base.WriteStartArray();
CheckDepth();
}
public override void WriteStartConstructor(string name)
{
base.WriteStartConstructor(name);
CheckDepth();
}
public override void WriteStartObject()
{
base.WriteStartObject();
CheckDepth();
}
uint checkDepthCounter = 0;
private void CheckDepth()
{
MaxObservedDepth = Math.Max(MaxObservedDepth, Top);
if (Top > MaxDepth)
throw new JsonSerializationException($"Depth {Top} Exceeds MaxDepth {MaxDepth} at path \"{Path}\"");
unchecked
{
if ((++checkDepthCounter & 0x3ff) == 0 && DateTime.Now - start > MaxTimeUsed)
throw new JsonSerializationException($"Time Usage Exceeded at path \"{Path}\"");
}
}
}
public class LocalStructuredLogging
{
public void LogStucturedEnd()
{
}
internal HashSet<long> disallowedToSerialize = new HashSet<long>();
public string TrySerialize<T>(T payload, int maxDepth = 100, int secondsToTimeout = 2, [CallerLineNumber] int line = 0)
{
long hashEl = typeof(T).Name.GetHashCode() * line;
if (disallowedToSerialize.Contains(hashEl))
{
return "°,°";
}
try
{
var settings = new JsonSerializerSettings { MaxDepth = maxDepth, ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore };
using (var writer = new StringWriter())
{
using (var jsonWriter = new TimedJsonTextWriter(writer, settings, new TimeSpan(0, 0, secondsToTimeout)))
{
JsonSerializer.Create(settings).Serialize(jsonWriter, payload);
// Log the MaxObservedDepth here, if you want to.
}
return writer.ToString();
}
}
catch (Exception)
{
disallowedToSerialize.Add(hashEl);
return "°-°";
}
}
}
The only issue remaining are the Hash collisions, which are easy to solve (e.g. by using the source file name as well or use another type of Collection).
The correct way to run an action timed would be to do something like the following. I would recommend taking a second look at how serialization should work as well :).
/// <summary>
/// Run an action timed.
/// </summary>
/// <param name="action">Action to execute timed.</param>
/// <param name="secondsTimout">Seconds before Task should cancel.</param>
/// <returns></returns>
public static async Task RunTimeout(Action action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
await Task.Run(action, tokenSource.Token);
}
You may also want to return a variable upon the completion of your timed task. That can be done like so...
public static async Task<T> RunTimeout<T>(Func<T> action, int secondsTimout) {
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(secondsTimout));
var result = await Task.Run(action, tokenSource.Token);
return result;
}
Related
I am using mock library in my .Net unit test and getting an error
cannot be accessed with an instance reference instead use type name.
I am getting this error at following line in my test method where it is calling cq.Instance. I am new to mock library. Could somebody let me know how do I call the static method?
attributeValue.Setup(cq => cq.Instance().CallQueryAsync(request, 1)).Returns(attrValue);
Actual method to be tested
public static async Task<AttributeValueList> GetAttributeSecDateValueList(int attrId)
{
try
{
var request = AttributeValue.ResolveRequest(attrId);
var response = await AsyncProxy<AttributeValue>.Instance().CallQueryAsync(request, (int)AttributeValue.OperationType.GetSecDateValues);
var coll = new AttributeValueList();
coll.AddRange(response);
return coll;
}
catch (Exception e)
{
throw e;
}
}
Proxy class
public class AsyncProxy<RT> : IDisposable
where RT : class, new()
{
readonly WebServiceProxy<RT> _wsProxy;
private AsyncProxy(WebServiceProxy<RT> webServiceProxy)
{
_wsProxy = webServiceProxy;
}
public static async Task<IEnumerable<RT>> Load(object parameters)
{
return await Instance().CallQueryAsync(parameters);
}
public static AsyncProxy<RT> Instance()
{
return new AsyncProxy<RT>(WebServiceProxy<RT>.Instance());
}
/// <summary>
/// Return result set of Poco as smartCollection
/// </summary>
public async Task<SmartCollection<RT>> CallQueryAsync(object request, int? uniqueIdentifier = null, bool isLongRunning = false, [CallerMemberName]string memberName = "")
{
//#if DEBUG
// var stopwatch = new Stopwatch();
// stopwatch.Start();
//#endif
try
{
// We want to get rid of the proxy as soon as we are done with it
using (_wsProxy)
{
var awaited = await _wsProxy.CallQueryAsync(request, uniqueIdentifier, isLongRunning);
if (awaited == null)
return null;
var observableCollection = new SmartCollection<RT>();
foreach (var item in awaited)
observableCollection.Add(item as RT);
return observableCollection;
}
}
finally
{
Dispose();
//#if DEBUG
// stopwatch.Stop();
// Debug.WriteLine(null);
// Debug.WriteLine($"****>>>>> AsyncProxy {memberName} took {stopwatch.ElapsedMilliseconds} ms <<<<<<<<****");
//#endif
}
}
}
test method
[TestMethod]
public void test()
{
Task<SmartCollection<AttributeValue>> attrValue = null;
var request = new AttributeValue();
var attributeValue = new Mock<AsyncProxy<AttributeValue>>();
attributeValue.Setup(cq => cq.Instance().CallQueryAsync(request, 1)).Returns(attrValue);
}
I am working on a C# application, and I have 2 (soon to be 3, 4 and more) methods which have such a similar structure they are begging to be converted to something more generic. Here are 2 samples, you will see the similarities.
Method 1:
public async Task<APIGatewayProxyResponse> McaEventStoreRecvdPointsCouponProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
try
{
string thisRequestId = Guid.NewGuid().ToString();
if (request.PathParameters.Any())
{
var cardNumber = request.PathParameters.FirstOrDefault(x => x.Key.ToLower() == "card_number").Value;
context.Logger.LogLine($"MCA Event store event [{cardNumber}]");
var restValueVoucher = JsonConvert.DeserializeObject<RootObjectRestValueVoucherPayload>(request.Body);
RestValueVoucherPayloadValidator validator = new RestValueVoucherPayloadValidator();
ValidationResult results = validator.Validate(restValueVoucher.Payload);
if (!results.IsValid) throw new SchemaValidationException(results.Errors);
var dbRestValueVoucher = restValueVoucher.Payload.Convert(restValueVoucher.Payload);
dbRestValueVoucher.CardNumber = cardNumber;
loyaltyContext.Add(dbRestValueVoucher);
int rowsAffected = await loyaltyContext.SaveChangesAsync();
context.Logger.LogLine($"Database changes applied {rowsAffected}");
return GenerateResponse(HttpStatusCode.OK, new EventStoreResponse(context,
RequestResponseTypes.EVENT_STORE, thisRequestId,
restValueVoucher.Payload));
}
else
{
return GenerateResponse(HttpStatusCode.OK, new TestResponse(context, RequestResponseTypes.TEST_REQUEST));
}
}
catch (SchemaValidationException schemaEx)
{
context.Logger.LogLine(schemaEx.Message);
return GenerateResponse(HttpStatusCode.BadRequest, schemaEx);
}
catch (Exception ex)
{
context.Logger.LogLine($"{ex}");
LcsException lcsException = new LcsException(ex);
return GenerateResponse(HttpStatusCode.BadRequest,
lcsException);
}
}
Method 2:
public async Task<APIGatewayProxyResponse> McaEventStoreTierChangeProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
try
{
string thisRequestId = Guid.NewGuid().ToString();
if (request.PathParameters.Any())
{
var cardNumber = request.PathParameters.FirstOrDefault(x => x.Key.ToLower() == "card_number").Value;
context.Logger.LogLine($"MCA Event store event [{cardNumber}]");
var tierChange = JsonConvert.DeserializeObject<RootObjectTierChangePayload>(request.Body);
TierChangePayloadValidator validator = new TierChangePayloadValidator();
ValidationResult results = validator.Validate(tierChange.Payload);
if (!results.IsValid) throw new SchemaValidationException(results.Errors);
var dbTierChange = tierChange.Payload.Convert(tierChange.Payload);
dbTierChange.CardNumber = cardNumber;
loyaltyContext.Add(dbTierChange);
int rowsAffected = await loyaltyContext.SaveChangesAsync();
context.Logger.LogLine($"Database changes applied {rowsAffected}");
return GenerateResponse(HttpStatusCode.OK, new EventStoreResponse(context,
RequestResponseTypes.EVENT_STORE, thisRequestId,
tierChange.Payload));
}
else
{
return GenerateResponse(HttpStatusCode.OK, new TestResponse(context, RequestResponseTypes.TEST_REQUEST));
}
}
catch (SchemaValidationException schemaEx)
{
context.Logger.LogLine(schemaEx.Message);
return GenerateResponse(HttpStatusCode.BadRequest, schemaEx);
}
catch (Exception ex)
{
context.Logger.LogLine($"{ex}");
LcsException lcsException = new LcsException(ex);
return GenerateResponse(HttpStatusCode.BadRequest,
lcsException);
}
}
I started to work on the generic method, and got this far:
private static TPayload ProcessTest<TPayload, TEvent>(TPayload payload, TEvent myevent, string body, AbstractValidator<TPayload> validator)
where TPayload : Payload
where TEvent : IEventStore
{
var test = JsonConvert.DeserializeObject<TPayload>(body);
ValidationResult results = validator.Validate(?)
}
My issue is with refactoring this line at the moment: ValidationResult results = validator.Validate(tierChange.Payload). tierChange is a JSON 'Root object' that allows me to accept incoming JSON in the following format:
{
"Message": {
"message-id": 1000,
"old-tier": "SISTERCLUB",
"new-tier": "DIAMOND",
"timestamp-of-change": "2020-07-27T00:00:00",
"anniversary-date": "2020-07-28T00:00:00"
}
}
The structure is very similar to the incoming JSON for Method 1, which is:
{
"Message": {
"message-id": 10000,
"redeemed-voucher-instance-id":123,
"new-voucher-instance-id":1234,
"initial-voucher-value": 5.00,
"rest-voucher-value":15.00,
"valid-from": "2020-07-27T00:00:00",
"valid-to": "2021-07-27T00:00:00",
"description": "$5 BIRTHDAY VOUCHER",
"unit": "AUD"
}
}
The .Payload is used to access the content inside the root object in both cases, (content which is unique to each object). Here is an example of the Tier Change Root object and Payload (apart from different properties within Payload, the other object is the same).
The root object:
public class RootObjectTierChangePayload
{
[JsonProperty(PropertyName = "Message")]
public TierChangePayload Payload { get; set; }
}
And the inner object:
public partial class TierChangePayload : Payload, ITransform<TierChangePayload, TierChange>, IEventStore
{
[JsonProperty(PropertyName = "message-id")]
public int MessageId { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "old-tier")]
public string OldTier { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "new-tier")]
public string NewTier { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "timestamp-of-change")]
public DateTime TimestampOfChange { get; set; }
/// <summary>
/// </summary>
[JsonProperty(PropertyName = "anniversary-date")]
public DateTime AnniversaryDate { get; set; }
public TierChange Convert(TierChangePayload source)
{
TierChange tierChange = new TierChange
{
CreatedTimestamp = Functions.GenerateDateTimeByLocale(),
ChangeTimestamp = null,
AnniversaryDate = this.AnniversaryDate,
MessageId = this.MessageId,
NewTierId = this.NewTier,
OldTierId = this.OldTier
};
return tierChange;
}
public string ToJson()
{
throw new NotImplementedException();
}
}
How can I adjust the objects I'm using so that I can better generalise them to suit the generic method? At the moment, I can't access .Payload in the generic method.
Update
In C# you can pass code blocks (delegates) to other code blocks as Action<T> type or Func<T> type (with a variable number of generic arguments).
Those types just encapsulate your code and are useful in the cases such as yours - where the method is almost the same save for a couple of lines. You can take those couple of lines and pass them as a parameter to the method.
Action<> is a code block that takes T arguments and returns void.
Func<> is a code block that takes 0 or several T1 arguments and returns a T result.
Note that when compiled, these code blocks turn into static methods, and are thus purely a syntactic sugar.
End update
So your generic method can look like this:
public async Task<APIGatewayProxyResponse> GenericMethod<T>(APIGatewayProxyRequest request, ILambdaContext context, Func<string, (T, ValidationResult, string)> validationFunc) where T: class
{
try
{
string thisRequestId = Guid.NewGuid().ToString();
if (request.PathParameters.Any())
{
var cardNumber = request.PathParameters.FirstOrDefault(x => x.Key.ToLower() == "card_number").Value;
context.Logger.LogLine($"MCA Event store event [{cardNumber}]");
var validationAndData = validationFunc(request.Body);
ValidationResult results = validationAndData.Item2;
if (!results.IsValid) throw new SchemaValidationException(results.Errors);
loyaltyContext.Add(validationAndData.Item1);
int rowsAffected = await loyaltyContext.SaveChangesAsync();
context.Logger.LogLine($"Database changes applied {rowsAffected}");
return GenerateResponse(HttpStatusCode.OK, new EventStoreResponse(context,
RequestResponseTypes.EVENT_STORE, thisRequestId,
validationAndData.Item3));
}
else
{
return GenerateResponse(HttpStatusCode.OK,
new TestResponse(context, RequestResponseTypes.TEST_REQUEST));
}
}
catch (SchemaValidationException schemaEx)
{
context.Logger.LogLine(schemaEx.Message);
return GenerateResponse(HttpStatusCode.BadRequest, schemaEx);
}
catch (Exception ex)
{
context.Logger.LogLine($"{ex}");
LcsException lcsException = new LcsException(ex);
return GenerateResponse(HttpStatusCode.BadRequest,
lcsException);
}
}
Then you can convert the other two like so:
public async Task<APIGatewayProxyResponse> McaEventStoreRecvdPointsCouponProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
return await GenericMethod(request, context, (body) => {
var restValueVoucher = JsonConvert.DeserializeObject<RootObjectRestValueVoucherPayload>(request.Body);
RestValueVoucherPayloadValidator validator = new RestValueVoucherPayloadValidator();
var dbRestValueVoucher = restValueVoucher.Payload.Convert(restValueVoucher.Payload);
dbRestValueVoucher.CardNumber = cardNumber;
return (dbRestValueVoucher, validator.Validate(restValueVoucher.Payload), restValueVoucher.Payload);
});
}
public async Task<APIGatewayProxyResponse> McaEventStoreTierChangeProxyResponse(APIGatewayProxyRequest request, ILambdaContext context)
{
return await GenericMethod(request, context, (body) => {
var tierChange = JsonConvert.DeserializeObject<RootObjectTierChangePayload>(request.Body);
TierChangePayloadValidator validator = new TierChangePayloadValidator();
var dbTierChange = tierChange.Payload.Convert(tierChange.Payload);
dbTierChange.CardNumber = cardNumber;
return (dbTierChange, validator.Validate(tierChange.Payload), tierChange.Payload);
});
}
If you make generic root object
public class RootObject<T>
{
[JsonProperty(PropertyName = "Message")]
public T Payload { get; set; }
}
It could work if the json can handle that.
var tierChange = JsonConvert.DeserializeObject<RootObject<TPayload>>(request.Body);
ValidationResult results = validator.Validate(tierChange.Payload);
I'm implementing the MailChimp.NET wrapper in both synchronous and asynchronous ways and calls are going through without a problem, BUT results tend to get lost in the synchronous methods. In other words, if I send 100 members to be added (by batches of 10 due to the simultaneous connections limit of the MailChimp API), all 100 will indeed be visible in my MC audience but I'll loose from 5 to 25% of the results on code side. Here's the concerned bit of my implementation :
public class MailChimpClient : IDisposable
{
private MailChimpManager _mcm;
private string _apiKey;
private bool _isDisposed;
private ConcurrentQueue<MailChimpMember> _updatedMembersQueue;
private ConcurrentQueue<MailChimpBaseException> _exceptionsQueue;
private const int BatchSize = 10;
private const int TaskDelay = 100;
private ConcurrentQueue<MailChimpMember> UpdatedMembersQueue
{
get { return _updatedMembersQueue = _updatedMembersQueue ?? new ConcurrentQueue<MailChimpMember>(); }
set { _updatedMembersQueue = value; }
}
private ConcurrentQueue<MailChimpBaseException> ExceptionsQueue
{
get { return _exceptionsQueue = _exceptionsQueue ?? new ConcurrentQueue<MailChimpBaseException>(); }
set { _exceptionsQueue = value; }
}
public MailChimpClient(string apiKey)
{
_apiKey = apiKey;
_mcm = new MailChimpManager(apiKey);
}
private async Task AddOrUpdateMember(MailChimpMember member, string listId)
{
try
{
var model = member.ToApiMember();
model = await _mcm.Members.AddOrUpdateAsync(listId, model);
UpdatedMembersQueue.Enqueue(new MailChimpMember(model));
await Task.Delay(TaskDelay);
}
catch (Exception ex)
{
var mccex = new MailChimpClientException($"Error adding/updating member \"{(member != null ? member.MailAddress.ToString() : "NULL")}\" to list with ID \"{listId}\".", ex);
ExceptionsQueue.Enqueue(mccex);
}
}
private MailChimpClientResult AddOrUpdateMemberRange(IEnumerable<MailChimpMember> members, string listId)
{
var batches = members.GetBatches(BatchSize);
var result = new MailChimpClientResult();
var i = 0;
foreach (var batch in batches)
{
AddOrUpdateMemberBatch(batch, listId);
i++;
FlushQueues(ref result);
}
return result;
}
private void AddOrUpdateMemberBatch(MailChimpMember[] batch, string listId)
{
Task.WaitAll(batch.Select(async b => await AddOrUpdateMember(b, listId)).ToArray(), -1);
}
private void FlushQueues(ref MailChimpClientResult result)
{
result.UpdatedMembers.FlushQueue(UpdatedMembersQueue);
result.Exceptions.FlushQueue(ExceptionsQueue);
}
public MailChimpClientResult AddOrUpdate(MailChimpMember member, string listId)
{
return AddOrUpdateMemberRange(new MailChimpMember[] { member }, listId);
}
public MailChimpClientResult AddOrUpdate(IEnumerable<MailChimpMember> members, string listId)
{
return AddOrUpdateMemberRange(members, listId);
}
}
public static class CollectionExtensions
{
public static T[][] GetBatches<T>(this IEnumerable<T> items, int batchSize)
{
var result = new List<T[]>();
var batch = new List<T>();
foreach (var t in items)
{
if (batch.Count == batchSize)
{
result.Add(batch.ToArray());
batch.Clear();
}
batch.Add(t);
}
result.Add(batch.ToArray());
batch.Clear();
return result.ToArray();
}
public static void FlushQueue<T>(this IList<T> list, ConcurrentQueue<T> queue)
{
T item;
while (queue.TryDequeue(out item))
list.Add(item);
}
}
MailChimpMember being a public copy of the MailChimp.NET Member. The problem seems to happen in the batch processing method, the Task.WaitAll(...) instruction firing its completion event before all calls are complete, therefore not all results are queued. I tried delaying the execution of each individual treatment with Task.Delay() but with little to no result.
Does anyone have an idea what is failing in my implementation ?
I'm currently developing a WIn IoT app for the Raspberry Pi and I have a significant problem there...I could really need some help solving that...
The problem I'm having is that I have different commands I send over the serial port and every command recieves different responses.
After sending a few commands (can be just one or it can be 15...it's basically random -.-) my app just stops accepting the commands to send or completely crashes when I invoke one of the commands. It happens rather randomly and when I restart the app it's working again.
At least most of the time... Sometimes the first restart doesn't do anything and I have to restart again...
Since the app will probably be used in a bigger project I want to guarantee flawless execution. I don't want to have a customer (or the tool we use to communicate with the Pi) to restart the whole app. It should just abort it's non-working operation, log an error and then be back up running.
I think it may be a possibility that the buffer overflows for whatever reason... But I don't know of any way to clear the buffer from code in UWP. And I can't seem to find anything regarding that.
If you can help me I would be really happy. I'm trying to get this to work for almost a week but I can't find the problem...
My colleague rewrote some of my code last week. I originally passed every info I needed through tuples but he told me it's not good. So he wrote the class containing the info for me. But since then I run into a lot of problems. But I really don't know why it doesn't...
If you want to know more about my code feel free to ask. I really need this to work :/
Some of the code I'm using:
This is the block containing my data:
public class InfoBlock
{
public List<InfoBlockValue> Names { get; set; }
public byte[] ReceivedCrcSum { get; set; }
public byte[] CalculatedCrcSum { get; set; }
public bool IsChecksumEqual { get; set; } = false;
}
public class InfoBlockValue
{
public string InfoString { get; set; }
public InfoBlockValue(string info)
{
this.InfoString = info;
}
public override string ToString()
{
return InfoString;
}
}
This is the class for my Read and Write operations:
public class GetInfoBlock
{
public string SendCommand { get; set; } = "##getnames";
public async Task<uint> Write()
{
byte CarriageReturn = 0x0D;
byte[] WriteArray = StringToByteArray(SendCommand);
byte[] WriteArrayCR = new byte[WriteArray.Length + 1];
WriteArray.CopyTo(WriteArrayCR, 0);
WriteArrayCR[WriteArray.Length] = CarriageReturn;
return await ReadWriteAdapter.Current.WriteAsync(WriteArrayCR);
}
public async Task<InfoBlock> Read()
{
InfoBlock block = new InfoBlock();
byte[] ListenOut = await ReadWriteAdapter.Current.Listen(5802);
byte[] NameCrcSource = new byte[5800];
byte[] NameCrc16 = new byte[2];
Array.Copy(ListenOut, 0, NameCrcSource, 0, 5800);
block.Names = ConvertFromBytes(NameCrcSource);
block.ReceivedCrcSum = new byte[] { ListenOut[5801], ListenOut[5800] };
block.CalculatedCrcSum = Crc16Ccitt.ComputeChecksumBytes(NameCrcSource);
block.IsChecksumEqual = ValidateDataCRC.ValidateData(NameCrcSource, block.ReceivedCrcSum);
return block;
}
public List<InfoBlockValue> ConvertFromBytes(byte[] dataFromDrive)
{
List<InfoBlockValue> InfoValue = new List<InfoBlockValue>();
string[] allConvertedIntegers = new String[100];
int lastReadByte = 0;
int parameterIndex = 0;
int parameterByteIndex = 0;
for (parameterIndex = 0; parameterIndex < 99; parameterIndex++)
{
byte[] allBytesOfOneParameter = new byte[28];
Array.Copy(dataFromDrive, lastReadByte + 1, allBytesOfOneParameter, 0, 28);
InfoValue.Add(new InfoBlockValue(System.Text.Encoding.UTF8.GetString(allBytesOfOneParameter)));
parameterByteIndex = 0;
lastReadByte += 29;
}
return InfoValue;
}
public byte[] StringToByteArray(string str)
{
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
return enc.GetBytes(str);
}
}
This is the code where I use the operations:
public class ReadInfo
{
private GetInfoBlock InfoBlock = null;
public async Task<Tuple<string, InfoBlock>> ReadNamesBlock()
{
if (InfoBlock == null)
{
InfoBlock = new GetInfoBlock();
}
await ReadWriteAdapter.semaphore.WaitAsync();
string returnString = string.Empty;
uint writeTuple = await InfoBlock.Write();
try
{
InfoBlock readTuple = await NamesBlock.Read();
bool validationResult = readTuple.IsChecksumEqual;
if (validationResult)
{
returnString += $"Checksum {BitConverter.ToString(readTuple.CalculatedCrcSum)} ReceivedCrcSum: {BitConverter.ToString(readTuple.ReceivedCrcSum)}";
//await ValidateDataCRC.SendAck();
}
else
{
returnString += "Checksum error";
await ValidateDataCRC.SendNack();
}
return new Tuple<string, InfoBlock>(returnString, readTuple);
}
catch (Exception ex)
{
string exception = $"Error while reading the parameternames from the device: {ex.Message}";
return new Tuple<string, InfoBlock>(exception, null);
}
finally
{
NamesBlock = null;
ReadWriteAdapter.semaphore.Release();
}
}
}
And the last one is my ReadWriteAdapter:
public class ReadWriteAdapter
{
public static SemaphoreSlim semaphore = new SemaphoreSlim(1);
private static readonly Object lockObject = new object();
private static ReadWriteAdapter instance;
public static ReadWriteAdapter Current
{
get
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new ReadWriteAdapter();
}
}
}
return instance;
}
}
private DataWriter dataWriter = null;
private DataReader dataReader = null;
private CancellationTokenSource ReadCancellationTokenSource;
private SerialDevice serialPort = null;
public bool IsDeviceInitialized()
{
return serialPort != null;
}
public async Task<string> Init()
{
try
{
string aqs = SerialDevice.GetDeviceSelector();
DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(aqs, null);
if (devices.Any())
{
if (devices[0].Id.Contains("FTDI"))
{
string deviceId = devices[0].Id;
await OpenPort(deviceId);
}
else
{
string deviceId = devices[1].Id;
await OpenPort(deviceId);
}
ReadCancellationTokenSource = new CancellationTokenSource();
dataWriter = new DataWriter(serialPort.OutputStream);
dataReader = new DataReader(serialPort.InputStream);
return "found port";
}
return "nodevices";
}
catch (Exception ex)
{
return ex.Message;
}
}
private async Task OpenPort(string deviceId)
{
serialPort = await SerialDevice.FromIdAsync(deviceId);
if (serialPort != null)
{
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(500);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(500);
serialPort.BaudRate = 19200;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.None;
}
}
private async Task<byte[]> ReadAsync(CancellationToken cancellationToken, uint ReadBufferLength)
{
Task<uint> loadAsyncTask;
byte[] returnArray = new byte[ReadBufferLength];
dataReader.InputStreamOptions = InputStreamOptions.Partial;
loadAsyncTask = dataReader.LoadAsync(ReadBufferLength).AsTask(cancellationToken); // Create a task object
uint bytesRead = await loadAsyncTask; // Launch the task and wait until buffer would be full
if (bytesRead > 0)
{
dataReader.ReadBytes(returnArray);
}
return returnArray;
}
public async Task<uint> WriteAsync(byte[] data)
{
if (serialPort == null)
{
throw new ArgumentNullException("device");
}
if (dataWriter == null)
{
throw new ArgumentNullException("device");
}
if (data.Length != 0)
{
dataWriter.WriteBytes(data);
// Launch an async task to complete the write operation
Task<uint> storeAsyncTask = dataWriter.StoreAsync().AsTask();
return await storeAsyncTask;
}
else
{
return 0;
}
}
public async Task<byte[]> Listen(uint BufferLength)
{
byte[] listen = new byte[BufferLength];
try
{
if (serialPort != null)
{
dataReader = new DataReader(serialPort.InputStream);
listen = await ReadAsync(ReadCancellationTokenSource.Token, BufferLength);
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (dataReader != null) // Cleanup once complete
{
dataReader.DetachStream();
dataReader = null;
}
}
return listen;
}
}
Found the answer myself...
I had to dispose the dataReader after I used it.
I guess the amount of streams I had on the reader just overflowed or whatever.
Changed this in ReadWriteAdapter:
finally
{
if (dataReader != null) // Cleanup once complete
{
dataReader.DetachStream();
dataReader = null;
}
}
To this:
finally
{
if (dataReader != null) // Cleanup once complete
{
dataReader.DetachStream();
dataReader.Dispose(); //Added this part
dataReader = null;
}
}
This is a basically a class library project which is somehow exposed as a WCF service. The code below is a part of the Data Access Layer. 'db' is an object of a DataContext class. To save a file, we do the following-
public static Guid SaveFile(FileDetails fileDetails)
{
System.Nullable<Guid> id = null;
SystemDataContext.UsingWrite(db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
});
return id ?? Guid.Empty;
}
Then, the below would execute-
public static void UsingWrite(Action<SoftCashCreditDBDataContext> action)
{
using (var context = new SystemDataContext())
{
try
{
action(context.Write);
}
catch (Exception ex)
{
DataAccessExceptionHandler.HandleExcetion(ex, Config.DataLayerPolicy);
}
}
}
public SystemDataContext()
{
if (_stack == null)
{
_stack = new Stack<SystemDataContext>();
this.Depth = 1;
this.Read = new SoftCashCreditDBDataContext(Config.ReadDatabaseConnection);
this.Write = new SoftCashCreditDBDataContext(Config.WriteDatabaseConnection);
}
else
{
var parent = _stack.Peek();
/// Increment level of node.
this.Depth = parent.Depth + 1;
/// Copy data context from the parent
this.Read = parent.Read;
this.Write = parent.Write;
}
_stack.Push(this);
}
public int Depth { get; private set; }
public bool IsRoot { get { return this.Depth == 1; } }
[ThreadStatic]
private static Stack<SystemDataContext> _stack = null;
public SoftCashCreditDBDataContext Read { get; private set; }
public SoftCashCreditDBDataContext Write { get; private set; }
#region IDisposable Members
public void Dispose()
{
var context = _stack.Pop();
if (context.IsRoot == true)
{
context.Read.Dispose();
context.Write.Dispose();
_stack = null;
}
}
#endregion
}
They have implemented LINQ to SQL here, and created a DBContext class. The 'SaveFileData()' method is actually part of that class, where it just calls an SP inside to save the file.
What I did not follow-
What exactly does the call to UsingWrite() do here? What is passed to the 'Action action' parameter, and what is it doing?
I understand your confusion. They use 2 delegates.
This is passed to the action parameter:
db =>
{
db.SaveFileData(fileDetails.RunId, fileDetails.FileData, fileDetails.FileExtension, ref id);
}
So when UsingWrite is called, the SoftCashCreditDBDataContext delegate which was set in the Write delegate will call SaveFileData.
A simplified example to help you understand Action:
public void Main()
{
Test(x => Debug.Write(x));
}
private void Test(Action<string> testAction)
{
testAction("Bla");
}
This function will call Debug.Write with the argument x, which is a string that is passed to the test action function.