Async method - weird behavior: my app is getting stuck - c#

I have a weird problem with my async method. I made second project in my solution in VS to connect my app to an API (using RestSharp for it). I made dependencies etc.
The problem is when I call this method from the UI by clicking a button (it only start backend code, there is no relation to UI etc.) app getting stuck. There is no errors, the only things I can see in the output window are "The thread ****** has exited with code 0 (0x0)." and it's going infinitely.
I took that code (only from project responsible for connecting and taking data from an api) and made a new solution, new project, but exacly copied code and it is working fine.
This is method what I am calling in the "main" WPF app using ICommand etc:
private void Api()
{
_orderService = new OrderService();
}
And those are classes in API project:
BLContext.cs
public class BLContext
{
private RestClient _client;
public RestClient Client { get; set; }
private string _token;
public string Token { get; set; }
public BLContext()
{
Client = new RestClient("https://api.baselinker.com/connector.php");
Token = "************************";
}
}
BaseAPIRepository.cs
public class BaseAPIRepository
{
private BLContext _bl = new BLContext();
RestRequest Request = new RestRequest();
public BaseAPIRepository() { }
public async Task<List<Order>> GetOrders()
{
List<Order> orders = new List<Order>();
List<JToken> orderList = new List<JToken>();
StartRequest("getOrders");
Request.AddParameter("parameters", "{ \"status_id\": 13595 }");
Request.AddParameter("parameters", "{ \"get_unconfirmed_orders\": false }");
RestResponse restResponse = await _bl.Client.PostAsync(Request);
JObject response = (JObject)JsonConvert.DeserializeObject(restResponse.Content);
orderList = response["orders"].ToList();
foreach (JToken order in orderList)
{
Order newOrder = new Order();
newOrder.Id = (int)order["order_id"];
newOrder.ProductsInOrder = GetProductsFromOrder((JArray)order["products"]);
orders.Add(newOrder);
}
return orders;
}
public void StartRequest(string method)
{
Request.AddParameter("token", _bl.Token);
Request.AddParameter("method", method);
}
public List<OrderedProduct> GetProductsFromOrder(JArray productsInOrder)
{
List<OrderedProduct> tmpListOfProducts = new List<OrderedProduct>();
foreach (var item in productsInOrder)
{
OrderedProduct tmpOrderedProduct = new OrderedProduct();
//tmpOrderedProduct.Id = (int)item["product_id"];
tmpOrderedProduct.Signature = (string)item["sku"];
tmpOrderedProduct.Quantity = (int)item["quantity"];
tmpListOfProducts.Add(tmpOrderedProduct);
}
return tmpListOfProducts;
}
}
OrderService.cs
public class OrderService
{
private BaseAPIRepository _repo;
private List<Order> _ordersList;
public List<Order> OrdersList { get; set; }
public OrderService()
{
_repo = new BaseAPIRepository();
OrdersList = new List<Order>();
OrdersList = _repo.GetOrders().Result;
Console.WriteLine("Test line to see if it passed 24th line.");
}
}
App is getting stuck on line:
RestResponse restResponse = await _bl.Client.PostAsync(Request);

The core problem - as others have noted - is that your code is blocking on asynchronous code, which you shouldn't do (as I explain on my blog). This is particularly true for UI apps, which deliver a bad user experience when the UI thread is blocked. So, even if the code wasn't deadlocking, it wouldn't be a good idea to block on the asynchronous code anyway.
There are certain places in a UI app where the code simply cannot block if you want a good user experience. View and ViewModel construction are two of those places. When a VM is being created, the OS is asking your app to display its UI right now, and waiting for a network request before displaying data is just a bad experience.
Instead, your application should initialize and return its UI immediately (synchronously), and display that. If you have to do a network request to get some data to display, it's normal to synchronously initialize the UI into a "loading" state, start the network request, and then at that point the construction/initialization is done. Later, when the network request completes, the UI is updated into a "loaded" state.
If you want to take this approach, there's a NotifyTask<T> type in my Nito.Mvvm.Async package which may help. Its design is described in this article and usage looks something like this (assuming OrderService is actually a ViewModel):
public class OrderService
{
private BaseAPIRepository _repo;
public NotifyTask<List<Order>> OrdersList { get; set; }
public OrderService()
{
_repo = new BaseAPIRepository();
OrdersList = NotifyTask.Create(() => _repo.GetOrders());
}
}
Then, instead of data-binding to OrderService.OrdersList, you can data-bind to OrderService.OrdersList.Result, OrderService.OrdersList.IsCompleted, etc.

You should never call Task.Result on an incomplete Task to avoid deadlocking the application. Always await a Task.
C# doesn't allow async constructors. Constructors are meant to return fast after some brief initialization. They are not a place for long-running operations or starting background threads (even if async constructors were allowed).
There are a few solutions to avoid the requirement of async constructors.
A simple alternative solution using Lazy<T> or AsyncLazy<T> (requires to install the Microsoft.VisualStudio.Threading package via the NuGet Package Manager). Lazy<T> allows to defer the instantiation or allocation of expensive resources.
public class OrderService
{
public List<object> Orders => this.OrdersInitializer.GetValue();
private AsyncLazy<List<object>> OrdersInitializer { get; }
public OrderService()
=> this.OrdersInitializer = new AsyncLazy<List<object>>(InitializeOrdersAsync, new JoinableTaskFactory(new JoinableTaskContext()));
private async Task<List<object>> InitializeOrdersAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
return new List<object> { 1, 2, 3 };
}
}
public static void Main()
{
var orderService = new OrderService();
// Trigger async initialization
orderService.Orders.Add(4);
}
You can expose the data using a method instead of a property
public class OrderService
{
private List<object> Orders { get; set; }
public async Task<List<object>> GetOrdersAsync()
{
if (this.Orders == null)
{
await Task.Delay(TimeSpan.FromSeconds(5));
this.Orders = new List<object> { 1, 2, 3 };
}
return this.Orders;
}
}
public static async Task Main()
{
var orderService = new OrderService();
// Trigger async initialization
List<object> orders = await orderService.GetOrdersAsync();
}
Use an InitializeAsync method that must be called before using the instance
public class OrderService
{
private List<object> orders;
public List<object> Orders
{
get
{
if (!this.IsInitialized)
{
throw new InvalidOperationException();
}
return this.orders;
}
private set
{
this.orders = value;
}
}
public bool IsInitialized { get; private set; }
public async Task<List<object>> InitializeAsync()
{
if (this.IsInitialized)
{
return;
}
await Task.Delay(TimeSpan.FromSeconds(5));
this.Orders = new List<object> { 1, 2, 3 };
this.IsInitialized = true;
}
}
public static async Task Main()
{
var orderService = new OrderService();
// Trigger async initialization
await orderService.InitializeAsync();
}
Instantiate the instance by passing the expensive arguments to the constructor
public class OrderService
{
public List<object> Orders { get; }
public async Task<List<object>> OrderService(List<object> orders)
=> this.Orders = orders;
}
public static async Task Main()
{
List<object> orders = await GetOrdersAsync();
// Instantiate with the result of the async operation
var orderService = new OrderService(orders);
}
private static async Task<List<object>> GetOrdersAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
return new List<object> { 1, 2, 3 };
}
Use a factory method and a private constructor
public class OrderService
{
public List<object> Orders { get; set; }
private OrderServiceBase()
=> this.Orders = new List<object>();
public static async Task<OrderService> CreateInstanceAsync()
{
var instance = new OrderService();
await Task.Delay(TimeSpan.FromSeconds(5));
instance.Orders = new List<object> { 1, 2, 3 };
return instance;
}
}
public static async Task Main()
{
// Trigger async initialization
OrderService orderService = await OrderService.CreateInstanceAsync();
}

Related

Integration testing with Events - Exceptions when running multiple tests at the same time

I am using a shared database fixture for my tests, but when running multiple tests at the same time, I get the following error message:
System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
This is my code of my Fixture:
public class SharedDatabaseFixture : IDisposable
{
public static readonly object _lock = new object();
private static bool _databaseInitialized;
private const string postgresConnectionString = "Host=localhost;Database=IntegrationTests; Username=postgres;Password=password";
public SharedDatabaseFixture()
{
Connection = new NpgsqlConnection(postgresConnectionString);
Seed();
Connection.Open();
}
public DbConnection Connection { get; }
public AppDbContext CreateContext(DbTransaction transaction = null!)
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkNpgsql()
.AddMediatR(typeof(IAggregateRoot).Assembly)
.AddScoped(typeof(IAsyncRepository<>), typeof(EfRepository<>))
.AddDbContext<AppDbContext>(options => options.UseNpgsql(Connection))
.BuildServiceProvider();
ServiceLocator.SetLocatorProvider(serviceProvider);
DomainEvents.Mediator = () => ServiceLocator.Current.GetInstance<IMediator>();
var builder = new DbContextOptionsBuilder<AppDbContext>();
builder.UseNpgsql(Connection).UseInternalServiceProvider(serviceProvider);
var context = new AppDbContext(builder.Options);
if (transaction != null)
{
context.Database.UseTransaction(transaction);
}
return context;
}
private void Seed()
{
lock (_lock)
{
if (!_databaseInitialized)
{
using (var context = CreateContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var appDbContextSeed = new AppDbContextSeed(context);
appDbContextSeed.SeedAsync().Wait();
}
_databaseInitialized = true;
}
}
}
public void Dispose() => Connection.Dispose();
}
The code I am testing uses events and those events do queries to the database. Therefore, I am registering some services and also a DbContext.
The problem is, when I run multiple tests at the same time, events are raised at the same time as well and because they are all using the same DbContext, it throws an exception when two handlers try to use the DbContext at the same time.
So, my question is: how can I instantiate a DbContext for each test (but using the same connection) or prevent it from using the DbContext at the same time?
An Example of one of my tests:
public class Project_Create : IClassFixture<SharedDatabaseFixture>
{
public SharedDatabaseFixture Fixture { get; }
public Project_Create(SharedDatabaseFixture fixture) => Fixture = fixture;
[Fact]
public void Creates_succesfully()
{
var project = new Project(SeedConstants.TEST_COMPANY_ID, "ABC", "Hallo123", "2018-123");
Assert.Equal(SeedConstants.TEST_COMPANY_ID, project.CompanyId);
Assert.Equal("ABC", project.Code);
Assert.Equal("Hallo123", project.Description);
Assert.Equal("2018-123", project.Number);
}
}
Project.cs:
public class Project : BaseEntity<Guid, ProjectValidator, Project>, IAggregateRoot
{
public Guid CompanyId { get; private set; }
public string Code { get; private set; }
public string Description { get; private set; }
public string Number { get; private set; }
public Project(Guid companyId, string code, string description, string number)
{
CompanyId = companyId;
Code = code;
Description = description;
Number = number;
Validate(this);
DomainEvents.Raise(new SetCompanyIdEvent(companyId)).GetAwaiter().GetResult();
}
}
As you can see, this project class raises an event. This event has a handler and looks like this:
public class CheckIfProjectIdExistsHandler : INotificationHandler<SetProjectIdEvent>
{
private readonly IAsyncRepository<Project> _projectRepository;
public CheckIfProjectIdExistsHandler(IAsyncRepository<Project> projectRepository)
{
_projectRepository = projectRepository;
}
public async Task Handle(SetProjectIdEvent notification, CancellationToken cancellationToken)
{
var project = await _projectRepository.GetByIdAsync(notification.ProjectId, cancellationToken);
if (project == null)
{
throw new ProjectDoesNotExistsException($"The project with ID {notification.ProjectId} does not exist.");
}
}
}
I hope this illustrates what I am testing
The answer is always simpler than you think.
When adding the DbContext in the Service Provider, I didn't specify the ServiceLifetime, so it is a singleton by default. Changing this to Transient solves the issue. Then the Connection should also be changed by the connectionString, so there are no multiple operations on the same connection.
So, this line:
.AddDbContext<AppDbContext>(options => options.UseNpgsql(Connection))
Should be change like so:
.AddDbContext<AppDbContext>(options => options.UseNpgsql(postgresConnectionString), ServiceLifetime.Transient)
Also, The registration of the repository should be as Transient and not Scoped.

Triggering DynamicData cache update using Reactive Subject

As a caveat I'm a novice with Rx (2 weeks) and have been experimenting with using Rx, RxUI and Roland Pheasant's DynamicData.
I have a service that initially loads data from local persistence and then, upon some user (or system) instruction will contact the server (TriggerServer in the example) to get additional or replacement data. The solution I've come up with uses a Subject and I've come across many a site discussing the pros/cons of using them. Although I understand the basics of hot/cold it's all based on reading rather than real world.
So, using the below as a simplified version, is this 'right' way of going about this problem or is there something I haven't properly understood somewhere?
NB: I'm not sure how important it is, but the actual code is taken from a Xamarin.Forms app, that uses RxUI, the user input being a ReactiveCommand.
Example:
using DynamicData;
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
public class MyService : IDisposable
{
private CompositeDisposable _cleanup;
private Subject<Unit> _serverSubject = new Subject<Unit>();
public MyService()
{
var data = Initialise().Publish();
AllData = data.AsObservableCache();
_cleanup = new CompositeDisposable(AllData, data.Connect());
}
public IObservableCache<MyData, Guid> AllData { get; }
public void TriggerServer()
{
// This is what I'm not sure about...
_serverSubject.OnNext(Unit.Default);
}
private IObservable<IChangeSet<MyData, Guid>> Initialise()
{
return ObservableChangeSet.Create<MyData, Guid>(async cache =>
{
// inital load - is this okay?
cache.AddOrUpdate(await LoadLocalData());
// is this a valid way of doing this?
var sync = _serverSubject.Select(_ => GetDataFromServer())
.Subscribe(async task =>
{
var data = await task.ConfigureAwait(false);
cache.AddOrUpdate(data);
});
return new CompositeDisposable(sync);
}, d=> d.Id);
}
private IObservable<MyData> LoadLocalData()
{
return Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => new MyData("localdata"));
}
private async Task<MyData> GetDataFromServer()
{
await Task.Delay(2000).ConfigureAwait(true);
return new MyData("serverdata");
}
public void Dispose()
{
_cleanup?.Dispose();
}
}
public class MyData
{
public MyData(string value)
{
Value = value;
}
public Guid Id { get; } = Guid.NewGuid();
public string Value { get; set; }
}
And a simple Console app to run:
public static class TestProgram
{
public static void Main()
{
var service = new MyService();
service.AllData.Connect()
.Bind(out var myData)
.Subscribe(_=> Console.WriteLine("data in"), ()=> Console.WriteLine("COMPLETE"));
while (Continue())
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine($"Triggering Server Call, current data is: {string.Join(", ", myData.Select(x=> x.Value))}");
service.TriggerServer();
}
}
private static bool Continue()
{
Console.WriteLine("Press any key to call server, x to exit");
var key = Console.ReadKey();
return key.Key != ConsoleKey.X;
}
}
Looks very good for first try with Rx
I would suggest few changes:
1) Remove the Initialize() call from the constructor and make it a public method - helps a lot with unit tests and now you can await it if you need to
public static void Main()
{
var service = new MyService();
service.Initialize();
2) Add Throttle to you trigger - this fixes parallel calls to the server returning the same results
3) Don't do anything that can throw in Subscribe, use Do instead:
var sync = _serverSubject
.Throttle(Timespan.FromSeconds(0.5), RxApp.TaskPoolScheduler) // you can pass a scheduler via arguments, or use TestScheduler in unit tests to make time pass faster
.Do(async _ =>
{
var data = await GetDataFromServer().ConfigureAwait(false); // I just think this is more readable, your way was also correct
cache.AddOrUpdate(data);
})
// .Retry(); // or anything alese to handle failures
.Subscribe();
I'm putting what I've come to as my solution just in case there's others that find this while they're wandering the internets.
I ended up removing the Subjects all together and chaining together several SourceCache, so when one changed it pushed into the other and so on. I've removed some code for brevity:
public class MyService : IDisposable
{
private SourceCache<MyData, Guid> _localCache = new SourceCache<MyData, Guid>(x=> x.Id);
private SourceCache<MyData, Guid> _serverCache = new SourceCache<MyData, Guid>(x=> x.Id);
public MyService()
{
var localdata = _localCache.Connect();
var serverdata = _serverCache.Connect();
var alldata = localdata.Merge(serverdata);
AllData = alldata.AsObservableCache();
}
public IObservableCache<MyData, Guid> AllData { get; }
public IObservable<Unit> TriggerLocal()
{
return LoadLocalAsync().ToObservable();
}
public IObservable<Unit> TriggerServer()
{
return LoadServerAsync().ToObservable();
}
}
EDIT: I've changed this again to remove any chaining of caches - I just manage the one cache internally. Lesson is not to post too early.

Object is not set in ViewModel class and control is directly move from data access class to View

I want to fetch first record from AboutUs table and display as a content label.
I have created 4 classes in MVVM pattern.
First is Model class AboutUs.cs
[Table("tblAboutUs")]
public class AboutUs
{
[PrimaryKey, AutoIncrement, NotNull]
public int IDP { get; set; }
public string Content { get; set; }
}
Second is Data Access class
SQLiteAboutUs.cs
public class SQLiteAboutUs
{
private static readonly AsyncLock Mutex = new AsyncLock();
private SQLiteAsyncConnection dbConn;
public int StatusCode { get; set; }
public SQLiteAboutUs(ISQLitePlatform sqlitePlatform, string dbPath)
{
if (dbConn == null)
{
var connectionFunc = new Func<SQLiteConnectionWithLock>(() =>
new SQLiteConnectionWithLock
(
sqlitePlatform,
new SQLiteConnectionString(dbPath, storeDateTimeAsTicks: false)
));
dbConn = new SQLiteAsyncConnection(connectionFunc);
dbConn.CreateTableAsync<Model.AboutUs>();
}
}
public SQLiteAboutUs()
{
}
public async Task Save(Model.AboutUs content)
{
using (await Mutex.LockAsync().ConfigureAwait(false))
{
StatusCode = 0;
await dbConn.InsertAsync(new Model.AboutUs { Content = content.Content });
StatusCode = 1;
}
//For Get first Row from Table
public async Task<Model.AboutUs> GetAllData()
{
return await dbConn.Table<Model.AboutUs>().Where(x => x.IDP == 1).FirstOrDefaultAsync();
}
}
Third class ViewModel Class
AboutUsViewModel.cs
public class AboutUsViewModel
{
readonly SQLiteAboutUs _db;
public string AboutUsContent { get; set; }
//public string AboutUs;
public AboutUsViewModel()
{
_db = new SQLiteAboutUs();
}
public async void FirstRecord()
{
Model.AboutUs obj = await _db.GetAllData();
this.AboutUsContent = obj.Content;
}
}
Forth one is Code behind file of my xaml pages.
AboutUs.xaml.cs
public partial class AboutUs : ContentPage
{
readonly AboutUsViewModel aboutUsViewModel;
public AboutUs()
{
InitializeComponent();
aboutUsViewModel = new AboutUsViewModel();
aboutUsViewModel.FirstRecord();
lblContent.Text = aboutUsViewModel.AboutUsContent;
}
}
I have debug code but problem is In AboutUsViewModel.cs class in FirstRecord Method object can not be set that's why AboutUsContent string property is also not set.
I can't figure out why my debugger directly jump from GetAllData() method in SQLiteAboutUs.cs to label.text in code behind file of view?
Welcome in the wonderfull world of asynchronicity. I encourage you to read carefully about how await is working: How and When to use `async` and `await`
It is not a blocking call. Thus you create the view AboutUs. It creates the ViewModel AboutUsViewModel. It calls
aboutUsViewModel.FirstRecord();
But does not wait for the call to be complete (dont't forget you marked your FirstRecord function as async...)
So it calls
Model.AboutUs obj = await _db.GetAllData();
And directly return to the caller because of the await operator.
That's why it directly jump to
lblContent.Text = aboutUsViewModel.AboutUsContent;
What you would like is Something like
await aboutUsViewModel.FirstRecord();
To wait the call to be complete before going to the next line. But of course you can't do that, because you are in a constructor and you can't have an async constructor. And calling a database (or actually anything that could likely failed) in a constructor is a bad practice anyway.
I would advise you to let only InitializeComponent() in the constructor, and then use Something like the OnDataLoaded() event of your view to perform your async call with a await.
Hope it helps.

Catch exceptions in async loading of dialog viewmodel

I have a DialogViewModel class with async Task LoadData() method. This method loads data asynchronously and shows this dialog, which notifies user about loading. Here is the code:
try
{
var dialog = new DialogViewModel();
var loadTask = dialog.LoadData();
WindowManager.ShowDialog(dialog);
await loadTask;
}
catch (Exception ex)
{
Logger.Error("Error in DialogViewModel", ex);
// Notify user about the error
}
When LoadData throws an exception, it isn't handled until user exits the dialog. It happens because exception is handled when calling await, and it's not happening until WindowManager.ShowDialog(dialog) completes.
What is the correct way to show a dialog with async loading? I've tried this ways:
Call LoadData() in OnShow(), constructor or similar. But this won't work if I'll need to show this dialog without any data
Call await LoadData() before showing the dialog. This way user have to wait for data to load before actually seeing the window, but I want the window to show up instantly with a loading indicator.
Why is there an explicit public LoadData method?
If this has to happen then do it inside the constructor asynchronously using Task<T> with a ContinueWith to process any exception generated by checking the IsFaultedproperty on the returned task.
This would address both issues you've highlighted.
A very simple example is shown below, obivously you're implementation will be more complicated.
public class DialogViewModel
{
private Task _task;
public DialogViewModel()
{
var context = TaskScheduler.FromCurrentSynchronizationContext();
_task = Task.Factory.StartNew(() =>
{
var data = GetDataCollection();
return data;
})
.ContinueWith(t =>
{
if (t.IsFaulted)
{
HasErrored = true;
ErrorMessage = "It's borked!";
}
else
{
Data = t.Result;
}
}, context);
}
public IEnumerable<string> Data { get; private set; }
public bool HasErrored { get; private set; }
public string ErrorMessage { get; private set; }
private static IEnumerable<string> GetDataCollection()
{
return new List<string>()
{
"John",
"Jack",
"Steve"
};
}
}
Or if you don't want to use Task<T> explicitly and want to use async\await functionality you could use a slightly different approach because you can't use async\await with a class constructor:
public class DialogViewModel
{
public IEnumerable<string> Data { get; private set; }
public bool HasErrored { get; private set; }
public string ErrorMessage { get; private set; }
async public static Task<DialogViewModel> BuildViewModelAsync()
{
try
{
var data = await GetDataCollection();
return new DialogViewModel(data);
}
catch (Exception)
{
return new DialogViewModel("Failed!");
}
}
private DialogViewModel(IEnumerable<string> data)
{
Data = data;
}
private DialogViewModel(string errorMessage)
{
HasErrored = true;
ErrorMessage = errorMessage;
}
private async static Task<IEnumerable<string>> GetDataCollection()
{
// do something async...
return await Task.Factory.StartNew(() => new List<string>()
{
"John",
"Jack",
"Steve"
});
}
}

Setting a class property on a long running thread

I am populating some class properties,
One of them involves serializing an Entity structure to a byte[] this takes some time so I wanted to do it in a thread.
The value never gets set as I assume the class and thread are now out of scope.
The code is below, any ideas would be appreciated
public class classA
{
public void DoSomething()
{
var classC = new ClassB().DoSomethingElse();
//SAVE CLASS c to database
var serialized = classC.GetSerializedDataTable(); // is always null unless i take out the task from class c
}
}
public class ClassB
{
public ClassC DoSomethingElse()
{
var classC = new ClassC();
classC.DataTableValue = new DataTable();
classC.SerializeToByteArray();
return classC;
}
}
public class ClassC
{
public DataTable DataTableValue { get; set; }
private byte[] serializedData;
public void SerializedDataTable()
{
new Task(() => this.serializedData = this.DataTableValue.SerializeToByteArray()).Start();
}
public byte[] GetSerializedDataTable()
{
return this.serializedData;
}
}
A Task is not just meant to be used to run code on another thread, it represents a logical unit of work that can return something once it is complete.
Your ClassC.GetSerializedDataTable() appears to be a perfect place to make use of a Task<byte[]> return type:
public class ClassC
{
public DataTable DataTableValue { get; set; }
private Task<byte[]> serializeDataTask;
public void SerializeDataTable()
{
serializeDataTask = Task.Factory.StartNew( () => this.DataTableValue.SerializeToByteArray() );
}
public Task<byte[]> GetSerializedDataTable()
{
// You can either throw or start it lazily if SerializeDataTable hasnt been called yet.
if ( serializeDataTask == null )
throw new InvalidOperationException();
return serializeDataTask;
}
}
And now your client code can utilize the Task return type in intelligent ways. If the result is already available, it can consume it immediately via Task.Result. Otherwise it can wait for it to complete, or perform some other work until it completes. The point is the calling code now has the context to take the most appropriate course of action.
Back to your example:
public void DoSomething()
{
var classC = new ClassB().DoSomethingElse();
//SAVE CLASS c to database
var serializeTask = classC.GetSerializedDataTable();
// will obtain result if available, will block current thread and wait for serialized data if task still running.
var serializedData = serializeTask.Result;
}

Categories

Resources