I am using NDepend and in the following code, it detects this code smell.
But if I add readonly, then it wont compile.
namespace todo
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
public static class DocumentDBRepository<T> where T : class
{
private static readonly string DatabaseId = ConfigurationManager.AppSettings["database"];
private static readonly string CollectionId = ConfigurationManager.AppSettings["collection"];
private static DocumentClient client;
public static async Task<T> GetItemAsync(string id)
{
try
{
Document document = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
return (T)(dynamic)document;
}
catch (DocumentClientException e)
{
if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return null;
}
else
{
throw;
}
}
}
public static async Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate)
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1 })
.Where(predicate)
.AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<T>());
}
return results;
}
public static async Task<Document> CreateItemAsync(T item)
{
return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item);
}
public static async Task<Document> UpdateItemAsync(string id, T item)
{
return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item);
}
public static async Task DeleteItemAsync(string id)
{
await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
}
public static void Initialize()
{
client = new DocumentClient(new Uri(ConfigurationManager.AppSettings["endpoint"]), ConfigurationManager.AppSettings["authKey"]);
CreateDatabaseIfNotExistsAsync().Wait();
CreateCollectionIfNotExistsAsync().Wait();
}
private static async Task CreateDatabaseIfNotExistsAsync()
{
try
{
await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId));
}
catch (DocumentClientException e)
{
if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
{
await client.CreateDatabaseAsync(new Database { Id = DatabaseId });
}
else
{
throw;
}
}
}
private static async Task CreateCollectionIfNotExistsAsync()
{
try
{
await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId));
}
catch (DocumentClientException e)
{
if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
{
await client.CreateDocumentCollectionAsync(
UriFactory.CreateDatabaseUri(DatabaseId),
new DocumentCollection { Id = CollectionId },
new RequestOptions { OfferThroughput = 1000 });
}
else
{
throw;
}
}
}
}
}
The warning line is:
private static DocumentClient client;
How would you recommend to fix this NDepend warning?
Rule Description:
This rule warns about static fields that are not declared as read-only.
In Object-Oriented-Programming the natural artifact to hold states that can be modified is instance fields. Such mutable static fields create confusion about the expected state at runtime and impairs the code testability since the same mutable state is re-used for each test.
More discussion on the topic can be found here: http://codebetter.com/patricksmacchia/2011/05/04/back-to-basics-usage-of-static-members/
Try moving the initialization to the declaration:
private static readonly DocumentClient client = new DocumentClient( . . . . );
Related
Is there a way to call SendAsync in OnConnect without leading to a deadlock? I'm not using .Wait or .Result and it still leads to a deadlock.
Edit:
The actual problem is that SendAsync is being called twice (once at OnConnect and once at Main). If I put a await Task.Delay(10000) before the second call in Main, it actually works good. How can I fix it? If there is no task delay, it basically hangs on await tcs.Task.ConfigureAwait(false), because it's being called twice and async void OnConnect is kinda "fire and forget", meaning that it's not waiting for the first SendAsync to complete, before it goes for the second call.
// Program.cs
var client = new Client(key, secret);
await client.StartAsync().ConfigureAwait(false);
await Task.Delay(3000); // This line fixes it, but it's kinda fake fix
await client.SendAsync(request).ConfigureAwait(false);
await client.SendAsync(request2).ConfigureAwait(false);
Console.ReadLine();
// Client.cs
public class Client
{
private static long _nextId;
private readonly WebSocketClient _webSocket;
private readonly ConcurrentDictionary<long, TaskCompletionSource<string>> _outstandingRequests = new();
...
public event EventHandler<ConnectEventArgs>? Connected;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public ValueTask StartAsync()
{
_client.Connected += OnConnect;
_client.MessageReceived += OnMessageReceived;
return _webSocket.StartAsync(); // there is a long-running `Task.Run` inside it, which keeps the web socket connection and its pipelines open.
}
private async void OnConnect(object? sender, ConnectEventArgs e)
{
await AuthAsync(...); // the problematic line
}
private void OnMessageReceived(object? sender, MessageReceivedEventArgs e)
{
... deserialization stuff
if (_requests.TryRemove(response.Id, out var tcs))
{
tcs.TrySetResult(message);
}
}
public ValueTask<TResponse?> SendAsync<TResponse>(JsonRpcRequest request)
{
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async ValueTask<TResponse?> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message).ConfigureAwait(false);
var response = await tcs.Task.ConfigureAwait(false); // it hangs here (deadlock)
return JsonSerializer.Deserialize<TResponse>(response);
}
}
public ValueTask<JsonRpcResponse?> AuthAsync(JsonRpcRequest request)
{
return SendAsync<JsonRpcResponse>(request);
}
private static long NextId()
{
return Interlocked.Increment(ref _nextId);
}
}
public sealed class WebSocketClient
{
private readonly AsyncManualResetEvent _sendSemaphore = new(false); // Nito.AsyncEx
private readonly WebSocketPipe _webSocket; // SignalR Web Socket Pipe
...
public event EventHandler<ConnectEventArgs>? Connected;
public event EventHandler<DisconnectEventArgs>? Disconnected;
public event EventHandler<MessageReceivedEventArgs>? MessageReceived;
public ValueTask StartAsync()
{
_ = Task.Run(async () =>
{
try
{
await CreatePolicy()
.ExecuteAsync(async () =>
{
await _webSocket.StartAsync(new Uri(_url), CancellationToken.None).ConfigureAwait(false);
Connected?.Invoke(this, new ConnectEventArgs());
_sendSemaphore.Set();
await ReceiveLoopAsync().ConfigureAwait(false);
})
.ConfigureAwait(false);
}
catch (Exception ex)
{
// Failed after all retries
Disconnected?.Invoke(this, new DisconnectEventArgs(ex));
}
});
return ValueTask.CompletedTask;
}
public async ValueTask SendAsync(string message)
{
await _sendSemaphore.WaitAsync().ConfigureAwait(false);
var encoded = Encoding.UTF8.GetBytes(message);
await _webSocket.Transport!.Output
.WriteAsync(new ArraySegment<byte>(encoded, 0, encoded.Length), CancellationToken.None)
.ConfigureAwait(false);
}
private IAsyncPolicy CreatePolicy()
{
var retryPolicy = Policy
.Handle<WebSocketException>()
.WaitAndRetryForeverAsync(_ => ReconnectInterval,
(exception, retryCount, calculatedWaitDuration) =>
{
_sendSemaphore.Reset();
Reconnecting?.Invoke(this, new ReconnectingEventArgs(exception, retryCount, calculatedWaitDuration));
return Task.CompletedTask;
});
return retryPolicy;
}
private async Task ReceiveLoopAsync()
{
while (true)
{
var result = await _webSocket.Transport!.Input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
var buffer = result.Buffer;
...
}
}
}
As mentioned in the comments, those web socket wrappers are using System.IO.Pipelines, which is incorrect. System.IO.Pipelines is a stream of bytes, so it's appropriate for (non-web) sockets; a web socket is a stream of messages, so something like System.Threading.Channels would be more appropriate.
You could try something like this, which I just typed up and haven't even run:
public sealed class ChannelWebSocket : IDisposable
{
private readonly WebSocket _webSocket;
private readonly Channel<Message> _input;
private readonly Channel<Message> _output;
public ChannelWebSocket(WebSocket webSocket, Options options)
{
_webSocket = webSocket;
_input = Channel.CreateBounded(new BoundedChannelOptions(options.InputCapacity)
{
FullMode = options.InputFullMode,
}, options.InputMessageDropped);
_output = Channel.CreateBounded(new BoundedChannelOptions(options.OutputCapacity)
{
FullMode = options.OutputFullMode,
}, options.OutputMessageDropped);
}
public ChannelReader<Message> Input => _input.Reader;
public ChannelWriter<Message> Output => _output.Writer;
public void Dispose() => _webSocket.Dispose();
public async void Start()
{
var inputTask = InputLoopAsync(default);
var outputTask = OutputLoopAsync(default);
var completedTask = await Task.WhenAny(inputTask, outputTask);
if (completedTask.Exception != null)
{
try { await _webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, statusDescription: null, default); } catch { /* ignore */ }
try { _input.Writer.Complete(completedTask.Exception); } catch { /* ignore */ }
try { _output.Writer.Complete(completedTask.Exception); } catch { /* ignore */ }
}
}
public sealed class Message
{
public WebSocketMessageType MessageType { get; set; }
public OwnedMemorySequence<byte> Payload { get; set; } = null!;
}
private async Task InputLoopAsync(CancellationToken cancellationToken)
{
while (true)
{
var payload = new OwnedMemorySequence<byte>();
var buffer = MemoryPool<byte>.Shared.Rent();
ValueWebSocketReceiveResult result;
do
{
result = await _webSocket.ReceiveAsync(buffer.Memory, cancellationToken);
if (result.MessageType == WebSocketMessageType.Close)
{
_input.Writer.Complete();
return;
}
payload.Append(buffer.Slice(0, result.Count));
} while (!result.EndOfMessage);
await _input.Writer.WriteAsync(new Message
{
MessageType = result.MessageType,
Payload = payload,
}, cancellationToken);
}
}
private async Task OutputLoopAsync(CancellationToken cancellationToken)
{
await foreach (var message in _output.Reader.ReadAllAsync())
{
var sequence = message.Payload.ReadOnlySequence;
if (sequence.IsEmpty)
continue;
while (!sequence.IsSingleSegment)
{
await _webSocket.SendAsync(sequence.First, message.MessageType, endOfMessage: false, cancellationToken);
sequence = sequence.Slice(sequence.First.Length);
}
await _webSocket.SendAsync(sequence.First, message.MessageType, endOfMessage: true, cancellationToken);
message.Payload.Dispose();
}
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, statusDescription: null, cancellationToken);
}
public sealed class Options
{
public int InputCapacity { get; set; } = 16;
public BoundedChannelFullMode InputFullMode { get; set; } = BoundedChannelFullMode.Wait;
public Action<Message>? InputMessageDropped { get; set; }
public int OutputCapacity { get; set; } = 16;
public BoundedChannelFullMode OutputFullMode { get; set; } = BoundedChannelFullMode.Wait;
public Action<Message>? OutputMessageDropped { get; set; }
}
}
It uses this type for building a memory sequence:
public sealed class MemorySequence<T>
{
private MemorySegment? _head;
private MemorySegment? _tail;
public MemorySequence<T> Append(ReadOnlyMemory<T> buffer)
{
if (_tail == null)
_head = _tail = new MemorySegment(buffer, runningIndex: 0);
else
_tail = _tail.Append(buffer);
return this;
}
public ReadOnlySequence<T> ReadOnlySequence => CreateReadOnlySequence(0, _tail?.Memory.Length ?? 0);
public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex) =>
_tail == null ? new ReadOnlySequence<T>(Array.Empty<T>()) :
new ReadOnlySequence<T>(_head!, firstBufferStartIndex, _tail, lastBufferEndIndex);
private sealed class MemorySegment : ReadOnlySequenceSegment<T>
{
public MemorySegment(ReadOnlyMemory<T> memory, long runningIndex)
{
Memory = memory;
RunningIndex = runningIndex;
}
public MemorySegment Append(ReadOnlyMemory<T> nextMemory)
{
var next = new MemorySegment(nextMemory, RunningIndex + Memory.Length);
Next = next;
return next;
}
}
}
and this type for building an owned memory sequence:
public sealed class OwnedMemorySequence<T> : IDisposable
{
private readonly CollectionDisposable _disposable = new();
private readonly MemorySequence<T> _sequence = new();
public OwnedMemorySequence<T> Append(IMemoryOwner<T> memoryOwner)
{
_disposable.Add(memoryOwner);
_sequence.Append(memoryOwner.Memory);
return this;
}
public ReadOnlySequence<T> ReadOnlySequence => _sequence.ReadOnlySequence;
public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex) =>
_sequence.CreateReadOnlySequence(firstBufferStartIndex, lastBufferEndIndex);
public void Dispose() => _disposable.Dispose();
}
which depends on owned memory span extension methods I stole from here:
public static class MemoryOwnerSliceExtensions
{
public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start, int length)
{
if (start == 0 && length == owner.Memory.Length)
return owner;
return new SliceOwner<T>(owner, start, length);
}
public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start)
{
if (start == 0)
return owner;
return new SliceOwner<T>(owner, start);
}
private sealed class SliceOwner<T> : IMemoryOwner<T>
{
private readonly IMemoryOwner<T> _owner;
public Memory<T> Memory { get; }
public SliceOwner(IMemoryOwner<T> owner, int start, int length)
{
_owner = owner;
Memory = _owner.Memory.Slice(start, length);
}
public SliceOwner(IMemoryOwner<T> owner, int start)
{
_owner = owner;
Memory = _owner.Memory[start..];
}
public void Dispose() => _owner.Dispose();
}
}
This code is all completely untested; use at your own risk.
In our application we use async calls. These calls we need to wait for so we use await. But we notice that the application continues the application some where else on a await from the HttpClient.SendAsync. We reproduced it with the following code:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace AsyncExperiment
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("1");
var adapter = new Adapter();
Console.WriteLine("2");
var result = Task.Factory.StartNew(() => adapter.Start()).Result;
Console.WriteLine("21");
Console.ReadKey();
}
}
public class Adapter
{
public async Task<string> Start()
{
Console.WriteLine("3");
return await CollectionAccessor.ExecuteWithinScope(async collection => {
Console.WriteLine("8");
var adapter = new AsyncSearchAdapter();
Console.WriteLine("9");
var result = await adapter.GetSearchAsync();
Console.WriteLine("19");
Console.WriteLine(result);
Console.WriteLine("20");
return "";
});
}
}
public class Client
{
public async Task<string> Get()
{
Console.WriteLine("12");
var requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://22ad5e1e-688d-4ba4-9287-6bb4a351fd05.mock.pstmn.io/test");
Console.WriteLine("13");
HttpClient httpClient = new HttpClient();
Console.WriteLine("14");
HttpResponseMessage response = await httpClient.SendAsync(requestMessage);
Console.WriteLine("15");
if(response.IsSuccessStatusCode){
Console.WriteLine("16a");
return await response.Content.ReadAsStringAsync();
}
Console.WriteLine("16b");
return null;
}
}
public class AsyncSearchAdapter
{
public async Task<string> GetSearchAsync()
{
Console.WriteLine("10");
var client = new Client();
Console.WriteLine("11");
var response = await client.Get();
Console.WriteLine("17");
if(response.Equals("{'test', 'test'}")){
Console.WriteLine("18a");
return response;
}
Console.WriteLine("18b");
return response;
}
}
public static class CollectionAccessor
{
public static TReturn ExecuteWithinScope<TReturn>(Func<ICatalogCollection, TReturn> func)
{
Console.WriteLine("4");
if(func == null) throw new ArgumentNullException("func");
Console.WriteLine("5");
using(var catalogCollection = Resolver())
{
Console.WriteLine("7");
return func(catalogCollection);
}
}
public static ICatalogCollection Resolver()
{
Console.WriteLine("6");
return new CatalogCollection();
}
}
public interface ICatalogCollection: IDisposable
{
string notImportant { get;}
}
public class CatalogCollection : ICatalogCollection, IDisposable
{
public string notImportant { get;}
public CatalogCollection(){
notImportant = "test";
}
public void Dispose()
{
throw new NotImplementedException();
}
}
}
We expect the order of the logs to be
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21
but we get the order like this:
1,2,3,4,5,6,7,8,9,10,11,12,13,14,21,15,16,17,18,19,20
Could someone explain to me why this is happening in this order. And how to get it in the expected order?
Thanks!!!
You are running async function (adapter.Start()) and not waiting for it. Try to change
var result = Task.Factory.StartNew(() => adapter.Start()).Result;
to
var result = adapter.Start().Result;
or
var result = Task.Factory.StartNew(() => adapter.Start().Result).Result;
and I guess you are doing same problem here
await CollectionAccessor.ExecuteWithinScope(async collection => {...})
just ensure that CollectionAccessor.ExecuteWithinScope will handle awaiting passed function into it. Like
async Task CollectionAccessor.ExecuteWithinScope(Func <ICollection, Task> action)
{
...
await (action(collection));
...
}
or at least returning it
async Task CollectionAccessor.ExecuteWithinScope(Func <ICollection, Task> action)
{
...
return (action(collection));
}
UPD
Right here
public static TReturn ExecuteWithinScope<TReturn>(Func<ICatalogCollection, TReturn> func)
{
Console.WriteLine("4");
if (func == null) throw new ArgumentNullException("func");
Console.WriteLine("5");
using (var catalogCollection = Resolver())
{
Console.WriteLine("7");
return func(catalogCollection); // <<<<<<<HERE
}
}
you are creating Task which is not finished yet and you returning it and disposing collection before task fininshed.
I guess you need to wait task for complete and only after it return it. Like
public static async Task<TReturn> ExecuteWithinScope<TReturn>(Func<ICatalogCollection, Task<TReturn>> func)
{
Console.WriteLine("4");
if (func == null) throw new ArgumentNullException("func");
Console.WriteLine("5");
using (var catalogCollection = Resolver())
{
Console.WriteLine("7");
return await func(catalogCollection); // waiting task for completition
}
}
OR you need to dispose collection inside task, like
public static TReturn ExecuteWithinScope<TReturn>(Func<ICatalogCollection, TReturn> func)
{
Console.WriteLine("4");
if (func == null) throw new ArgumentNullException("func");
Console.WriteLine("5");
//using (var catalogCollection = Resolver()) // not in using!
{
var catalogCollection = Resolver();
Console.WriteLine("7");
return func(catalogCollection);
}
}
And then
return await CollectionAccessor.ExecuteWithinScope(async collection =>
{
Console.WriteLine("8");
var adapter = new AsyncSearchAdapter();
Console.WriteLine("9");
var result = await adapter.GetSearchAsync();
Console.WriteLine("19");
Console.WriteLine(result);
Console.WriteLine("20");
collection.Dispose(); //// Disposing!
return "";
});
From my point first approach (await func(catalogCollection);) - is best one
I creating a UWP app that pulls in data from an easy table using IMobileServiceSyncTable, but when I try and use this to get information from the table to put into a list I get a SyncContext is not yet initialized error. I can add items to the database fine its just receiving things thats giving me trouble.
Heres my code for interacting with the table:
private MobileServiceCollection<DrillItem, DrillItem> drills;
private IMobileServiceSyncTable<DrillItem> drillTable = App.MobileService.GetSyncTable<DrillItem>();
public CombatDrillsTable()
{
}
public MobileServiceCollection<DrillItem, DrillItem> GetDrills()
{
return this.drills;
}
public async Task AddDrill(DrillItem drillItem, String n, int s, int t, string sty)
{
drillItem.Name = n;
drillItem.Sets = s;
drillItem.SetTime = t;
drillItem.Style = sty;
await App.MobileService.GetTable<DrillItem>().InsertAsync(drillItem);
drills.Add(drillItem);
}
public async void GetById(string n)
{
IMobileServiceTableQuery<DrillItem> query = drillTable.Where(drillItem => drillItem.Name == n)
.Select(drillItem => drillItem);
List<DrillItem> items = await query.ToListAsync();
Console.WriteLine(items);
}
public async Task GetDrillsAsync(String cat)
{
MobileServiceInvalidOperationException exception = null;
try {
drills = await drillTable.Where(drillItem => drillItem.Style == cat)
.ToCollectionAsync();
Console.WriteLine(drills);
}
catch (MobileServiceInvalidOperationException e)
{
exception = e;
}
if (exception != null)
{
await new MessageDialog(exception.Message, "Error loading items").ShowAsync();
}
else
{
// code here
}
}
heres the code that activates the get:
String parameters;
CombatTableView ctv = new CombatTableView();
private ObservableCollection<DrillItem> _items;
private ObservableCollection<DrillItem> _temp;
public DisplayDrills()
{
this.InitializeComponent();
_items = new ObservableCollection<DrillItem>();
AddItemsAsync();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
parameters = (String)e.Parameter;
testBox.Text = parameters;
RefreshListView.ItemsSource = _items;
}
private async Task updateDrillsAsync(){
await ctv.combatDrillsTable.GetDrillsAsync(parameters);
}
private async void AddItemsAsync()
{
await updateDrillsAsync();
_temp = ctv.combatDrillsTable.GetDrills();
foreach (var t in _temp)
{
_items.Insert(0, t);
}
}
As the exception described, we must initialize the local SQLite database before we can use the offline client. Details please reference Configuring the Local SQLite database. For example:
private async Task InitLocalStoreAsync()
{
if (!App.MobileService.SyncContext.IsInitialized)
{
var store = new MobileServiceSQLiteStore("localstore.db");
store.DefineTable<TodoItem>();
await App.MobileService.SyncContext.InitializeAsync(store);
}
await SyncAsync();
}
private async Task SyncAsync()
{
await App.MobileService.SyncContext.PushAsync();
await todoTable.PullAsync("todoItems", todoTable.CreateQuery());
}
And the best place to do this is in the GetTable<> method, the following code showed the example for initialize it when OnNavigatedTo, :
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
await InitLocalStoreAsync(); // offline sync
ButtonRefresh_Click(this, null);
}
If I start with a full console app:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace ConsoleApplication3
{
internal class Program
{
private static void Main(string[] args)
{
var tas = new List<TaskAuditor<string>>();
// I want to await these when they actually run so another thread can use it while
// wait for (example) my EF/SQL to run Async
tas.Add(new TaskAuditor<string>(await GetName(string.Empty, 1)));
tas.Add(new TaskAuditor<string>(await GetName(string.Empty, 2)));
var running = new Task<String>[2];
foreach (var ta in tas)
{
running[0] = ta.Start();
}
var runningCount = tas.Count;
while (runningCount > 0)
{
var idx = Task.WaitAny(running);
runningCount--;
var task = running[idx];
var ta = tas.FirstOrDefault(t => t.Task == task);
Console.WriteLine(ta.Duration.ToString());
}
foreach (var ta in tas)
{
Console.WriteLine(ta.Task.Result);
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
public async Task<string> GetName(string constr, int id)
{
string result = id.ToString();
// EF/SQL Async goes here
if (string.IsNullOrWhiteSpace(constr))
{
await Task.Delay(1000 * id);
}
return result;
}
}
public class TaskAuditor<T>
{
private Task<T> _task;
private Stopwatch _sw = new Stopwatch();
public TaskAuditor(Task<T> task)
{
_task = task;
}
public Task<T> Start()
{
_sw.Start();
_task.Start();
_sw.Stop();
return _task;
}
public TimeSpan? Duration()
{
TimeSpan? result = null;
if (!_sw.IsRunning)
{
result = _sw.Elapsed;
}
return result;
}
public Task<T> Task
{
get
{
return _task;
}
}
}
}
DotNetFiddle Sample.
The problem is that I need to await the method and it turns into an Async nightmare I can't quite figure out.
First, your auditor needs some work. As I describe on my blog, you can't call Start on a Promise Task. If you want to represent an operation that results in a task, then use Func<Task<T>>. The next problem is that you're stopping the stopwatch before the task actually completes:
public class TaskAuditor<T>
{
private Func<Task<T>> _func;
private Stopwatch _sw = new Stopwatch();
public TaskAuditor(Func<Task<T>> func)
{
_func = func;
}
public async Task<T> StartAsync()
{
_sw.Start();
try
{
return await _func();
}
finally
{
_sw.Stop();
}
}
public TimeSpan? Duration()
{
TimeSpan? result = null;
if (!_sw.IsRunning)
{
result = _sw.Elapsed;
}
return result;
}
}
Next, your calling method should use modern conveniences like Task.WhenAll, and a separate method to handle the result as they each complete instead of trying to pull the results out of a list:
private static async Task<string> ProcessAsync(TaskAuditor<string> auditor)
{
try
{
return await auditor.StartAsync();
}
finally
{
Console.WriteLine(auditor.Duration().ToString());
}
}
private static async Task MainAsync()
{
var tas = new List<TaskAuditor<string>>();
tas.Add(new TaskAuditor<string>(() => GetName(string.Empty, 1)));
tas.Add(new TaskAuditor<string>(() => GetName(string.Empty, 2)));
var running = tas.Select(ta => ProcessAsync(ta));
var results = await Task.WhenAll(running);
foreach (var result in results)
{
Console.WriteLine(result);
}
}
This also keeps the code asynchronous up to the point where it can't be asynchronous, namely, Main:
private static void Main()
{
MainAsync().Wait();
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
I've been trying to solve this silly problem for a few days now and it is doing my head in. I would be very happy is somebody has a working example, because the ones I have found so far did not work :(
I can serialize basic types, but not objects. And I am very confused in regards to the DataContractAttribute etc. The error I get is:
{"Type 'SerializeListWinRT.DataModel.LocalStorage+Cat' with data
contract name
'LocalStorage.Cat:http://schemas.datacontract.org/2004/07/SerializeListWinRT.DataModel'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to DataContractSerializer."}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using System.IO;
using System.Runtime.Serialization;
using Windows.Storage.Streams;
namespace SerializeListWinRT.DataModel
{
class LocalStorage
{
[DataContractAttribute]
public class Cat
{
[DataMember()]
public String Name { get; set; }
}
static private Dictionary<string, object> _data = new Dictionary<string, object>();
private const string filename = "items.xml";
static public Dictionary<string, object> Data
{
get { return _data; }
}
static public T GetItem<T>(string key)
{
T result = default(T);
if (_data.ContainsKey(key))
{
result = (T)_data[key];
}
return result;
}
static public bool ContainsItem(string key)
{
return _data.ContainsKey(key);
}
static async public Task Save()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) =>
{
LocalStorage.SaveAsync().Wait();
}, Windows.System.Threading.WorkItemPriority.Normal);
}
static async public Task Restore()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) =>
{
LocalStorage.RestoreAsync().Wait();
}, Windows.System.Threading.WorkItemPriority.Normal);
}
static async private Task SaveAsync()
{
_data.Add("cat", new Cat { Name = "Myname is" });
_data.Add("dog", new Cat { Name = "Myname is" });
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
IRandomAccessStream sessionRandomAccess = await sessionFile.OpenAsync(FileAccessMode.ReadWrite);
IOutputStream sessionOutputStream = sessionRandomAccess.GetOutputStreamAt(0);
DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Dictionary<string, object>));
sessionSerializer.WriteObject(sessionOutputStream.AsStreamForWrite(), _data);
await sessionOutputStream.FlushAsync();
}
static async private Task RestoreAsync()
{
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists);
if (sessionFile == null)
{
return;
}
IInputStream sessionInputStream = await sessionFile.OpenReadAsync();
DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Dictionary<string, object>));
_data = (Dictionary<string, object>)sessionSerializer.ReadObject(sessionInputStream.AsStreamForRead());
}
}
}
How I solved the problem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using System.IO;
using System.Runtime.Serialization;
using Windows.Storage.Streams;
namespace SerializeListWinRT.DataModel
{
class LocalStorage
{
[KnownType(typeof(SerializeListWinRT.Cat))]
[DataContractAttribute]
public class Cat
{
[DataMember()]
public String Name { get; set; }
}
static private Dictionary<string, object> _data = new Dictionary<string, object>();
private const string filename = "ngt.xml";
static public Dictionary<string, object> Data
{
get { return _data; }
}
static public T GetItem<T>(string key)
{
T result = default(T);
if (_data.ContainsKey(key))
{
result = (T)_data[key];
}
return result;
}
static public bool ContainsItem(string key)
{
return _data.ContainsKey(key);
}
static async public Task Save<T>()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) =>
{
LocalStorage.SaveAsync<T>().Wait();
}, Windows.System.Threading.WorkItemPriority.Normal);
}
static async public Task Restore<T>()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) =>
{
LocalStorage.RestoreAsync<T>().Wait();
}, Windows.System.Threading.WorkItemPriority.Normal);
}
static async private Task SaveAsync<T>()
{
_data.Add("cat", new Cat { Name = "Myname is" });
_data.Add("dog", new Cat { Name = "Myname is" });
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
IRandomAccessStream sessionRandomAccess = await sessionFile.OpenAsync(FileAccessMode.ReadWrite);
IOutputStream sessionOutputStream = sessionRandomAccess.GetOutputStreamAt(0);
DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Dictionary<string, object>), new Type[] { typeof(T) });
sessionSerializer.WriteObject(sessionOutputStream.AsStreamForWrite(), _data);
await sessionOutputStream.FlushAsync();
}
static async private Task RestoreAsync<T>()
{
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists);
if (sessionFile == null)
{
return;
}
IInputStream sessionInputStream = await sessionFile.OpenReadAsync();
DataContractSerializer sessionSerializer = new DataContractSerializer(typeof(Dictionary<string, object>), new Type[] { typeof(T) });
_data = (Dictionary<string, object>)sessionSerializer.ReadObject(sessionInputStream.AsStreamForRead());
}
}
}
I solved this by adding the KnownType attribute
[KnownType(typeof(SerializeListWinRT.Cat))]
But I havent had to do that before so I was'nt sure if it was a WinRT related problem. Oh well, it does work now,- but I am still curious as to why you have to decorate with the KnownType attribute..