What'd be the most elegant way to call an async method from a getter or setter in C#?
Here's some pseudo-code to help explain myself.
async Task<IEnumerable> MyAsyncMethod()
{
return await DoSomethingAsync();
}
public IEnumerable MyList
{
get
{
//call MyAsyncMethod() here
}
}
There is no technical reason that async properties are not allowed in C#. It was a purposeful design decision, because "asynchronous properties" is an oxymoron.
Properties should return current values; they should not be kicking off background operations.
Usually, when someone wants an "asynchronous property", what they really want is one of these:
An asynchronous method that returns a value. In this case, change the property to an async method.
A value that can be used in data-binding but must be calculated/retrieved asynchronously. In this case, either use an async factory method for the containing object or use an async InitAsync() method. The data-bound value will be default(T) until the value is calculated/retrieved.
A value that is expensive to create, but should be cached for future use. In this case, use AsyncLazy from my blog or AsyncEx library. This will give you an awaitable property.
Update: I cover asynchronous properties in one of my recent "async OOP" blog posts.
You can't call it asynchronously, since there is no asynchronous property support, only async methods. As such, there are two options, both taking advantage of the fact that asynchronous methods in the CTP are really just a method that returns Task<T> or Task:
// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
get
{
// Just call the method
return MyAsyncMethod();
}
}
Or:
// Make the property blocking
public IEnumerable MyList
{
get
{
// Block via .Result
return MyAsyncMethod().Result;
}
}
I really needed the call to originate from the get method, due to my decoupled architecture. So I came up with the following implementation.
Usage: Title is in a ViewModel or an object you could statically declare as a page resource. Bind to it and the value will get populated without blocking the UI, when getTitle() returns.
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can use Task like this :
public int SelectedTab
{
get => selected_tab;
set
{
selected_tab = value;
new Task(async () =>
{
await newTab.ScaleTo(0.8);
}).Start();
}
}
I think that we can await for the value just returning first null and then get the real value, so in the case of Pure MVVM (PCL project for instance) I think the following is the most elegant solution:
private IEnumerable myList;
public IEnumerable MyList
{
get
{
if(myList == null)
InitializeMyList();
return myList;
}
set
{
myList = value;
NotifyPropertyChanged();
}
}
private async void InitializeMyList()
{
MyList = await AzureService.GetMyList();
}
I thought .GetAwaiter().GetResult() was exactly the solution to this problem, no?
eg:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
_Title = getTitle().GetAwaiter().GetResult();
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Since your "async property" is in a viewmodel, you could use AsyncMVVM:
class MyViewModel : AsyncBindableBase
{
public string Title
{
get
{
return Property.Get(GetTitleAsync);
}
}
private async Task<string> GetTitleAsync()
{
//...
}
}
It will take care of the synchronization context and property change notification for you.
You can create an event and invoke an event when the property is changed.
Something like this:
private event EventHandler<string> AddressChanged;
public YourClassConstructor(){
AddressChanged += GoogleAddressesViewModel_AddressChanged;
}
private async void GoogleAddressesViewModel_AddressChanged(object sender, string e){
... make your async call
}
private string _addressToSearch;
public string AddressToSearch
{
get { return _addressToSearch; }
set
{
_addressToSearch = value;
AddressChanged.Invoke(this, AddressToSearch);
}
}
When I ran into this problem, trying to run an async method synchronicity from either a setter or a constructor got me into a deadlock on the UI thread, and using an event handler required too many changes in the general design.
The solution was, as often is, to just write explicitly what I wanted to happen implicitly, which was to have another thread handle the operation and to get the main thread to wait for it to finish:
string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();
You could argue that I abuse the framework, but it works.
Necromancing.
In .NET Core/NetStandard2, you can use Nito.AsyncEx.AsyncContext.Run instead of System.Windows.Threading.Dispatcher.InvokeAsync:
class AsyncPropertyTest
{
private static async System.Threading.Tasks.Task<int> GetInt(string text)
{
await System.Threading.Tasks.Task.Delay(2000);
System.Threading.Thread.Sleep(2000);
return int.Parse(text);
}
public static int MyProperty
{
get
{
int x = 0;
// https://stackoverflow.com/questions/6602244/how-to-call-an-async-method-from-a-getter-or-setter
// https://stackoverflow.com/questions/41748335/net-dispatcher-for-net-core
// https://github.com/StephenCleary/AsyncEx
Nito.AsyncEx.AsyncContext.Run(async delegate ()
{
x = await GetInt("123");
});
return x;
}
}
public static void Test()
{
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
System.Console.WriteLine(MyProperty);
System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
}
}
If you simply chose System.Threading.Tasks.Task.Run or System.Threading.Tasks.Task<int>.Run, then it wouldn't work.
I think my example below may follow #Stephen-Cleary 's approach but I wanted to give a coded example. This is for use in a data binding context for example Xamarin.
The constructor of the class - or indeed the setter of another property on which it is dependent - may call an async void that will populate the property on completion of the task without the need for an await or block. When it finally gets a value it will update your UI via the NotifyPropertyChanged mechanism.
I'm not certain about any side effects of calling a aysnc void from a constructor. Perhaps a commenter will elaborate on error handling etc.
class MainPageViewModel : INotifyPropertyChanged
{
IEnumerable myList;
public event PropertyChangedEventHandler PropertyChanged;
public MainPageViewModel()
{
MyAsyncMethod()
}
public IEnumerable MyList
{
set
{
if (myList != value)
{
myList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
}
get
{
return myList;
}
}
async void MyAsyncMethod()
{
MyList = await DoSomethingAsync();
}
}
I review all answer but all have a performance issue.
for example in :
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
use dispatcher which is not a good answer.
but there is a simple solution, just do it:
string _Title;
public string Title
{
get
{
if (_Title == null)
{
Task.Run(()=>
{
_Title = getTitle();
RaisePropertyChanged("Title");
});
return;
}
return _Title;
}
set
{
if (value != _Title)
{
_Title = value;
RaisePropertyChanged("Title");
}
}
}
You can change the proerty to Task<IEnumerable>
and do something like:
get
{
Task<IEnumerable>.Run(async()=>{
return await getMyList();
});
}
and use it like
await MyList;
Related
I'm implementing my own version of auto-complete in a view model in my Xamarin Forms 5 app.
I need to call an async look up function from the Keyword MVVM property. Not sure if I'm handling it right. See below:
string keyword { get; set; }
ObservableRangeCollection<string> suggestions = new ObservableRangeCollection<string>();
public string Keyword
{
get => keyword;
set
{
if(keyword == value)
return;
keyword = value;
OnPropertyChanged();
// If keyword has at least 3 characters, get suggestions
if(keyword.length > 2)
GetSuggestions(keyword).Wait();
}
}
ObservableRangeCollection<string> Suggestions
{
get => suggestions;
set
{
if(sugggestions == value)
return;
suggestions = value;
OnPropertyChanged();
}
}
async Task GetSuggestions(string searchKeyword)
{
var result = await _myApiService.GetSuggestions(searchKeyword);
if(result != null)
{
Suggestions = new ObservableRangeCollection(result);
OnPropertyChanged(Suggestions);
}
}
I'd appreciate any corrections or suggestions. Thanks.
You definitely don't want to block on async code here (as explained on my blog). More generally, you don't want to block the UI, especially when your users are interacting with it (i.e., typing).
Instead, use a pattern like async data binding to (synchronously) start the operation, and then update your UI when the results come in.
E.g., using NotifyTask<T> from here:
string keyword { get; set; }
NotifyTask<List<string>> suggestions;
public string Keyword
{
get => keyword;
set
{
if (keyword == value)
return;
keyword = value;
OnPropertyChanged();
// If keyword has at least 3 characters, get suggestions
if (keyword.length > 2)
suggestions = NotifyTask.Create(GetSuggestionsAsync(keyword));
}
}
NotifyTask<List<string>> Suggestions
{
get => suggestions;
}
async Task<List<string>> GetSuggestionsAsync(string searchKeyword)
{
return await _myApiService.GetSuggestions(searchKeyword);
}
Then, instead of data-binding to Suggestions as a collection of string, data-bind to Suggestions.Result instead. You can also data-bind to Suggestions.IsCompleted (or Suggestions.IsNotCompleted) to show a loading/busy indicator, and Suggestions.IsFaulted to show an error message.
I working on real-time search. At this moment on property setter which is bounded to edit text, I call a method which calls API and then fills the list with the result it looks like this:
private string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
SetProperty(ref searchPhrase, value);
RunOnMainThread(SearchResult.Clear);
isAllFriends = false;
currentPage = 0;
RunInAsync(LoadData);
}
}
private async Task LoadData()
{
var response = await connectionRepository.GetConnections(currentPage,
pageSize, searchPhrase);
foreach (UserConnection uc in response)
{
if (uc.Type != UserConnection.TypeEnum.Awaiting)
{
RunOnMainThread(() =>
SearchResult.Add(new ConnectionUser(uc)));
}
}
}
But this way is totally useless because of it totally mashup list of a result if a text is entering quickly. So to prevent this I want to run this method async in a property but if a property is changed again I want to kill the previous Task and star it again. How can I achieve this?
Some informations from this thread:
create a CancellationTokenSource
var ctc = new CancellationTokenSource();
create a method doing the async work
private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
return Task.Run(() =>
{
token.ThrowIfCancellationRequested();
// more code here
// check again if this task is canceled
token.ThrowIfCancellationRequested();
// more code
}
}
It is important to have this checks for cancel in the code.
Execute the function:
var cancellable = ExecuteLongCancellableMethod(ctc.Token);
To stop the long running execution use
ctc.Cancel();
For further details please consult the linked thread.
This question can be answered in many different ways. However IMO I would look at creating a class that
Delays itself automatically for X (ms) before performing the seach
Has the ability to be cancelled at any time as the search request changes.
Realistically this will change your code design, and should encapsulate the logic for both 1 & 2 in a separate class.
My initial thoughts are (and none of this is tested and mostly pseudo code).
class ConnectionSearch
{
public ConnectionSearch(string phrase, Action<object> addAction)
{
_searchPhrase = phrase;
_addAction = addAction;
_cancelSource = new CancellationTokenSource();
}
readonly string _searchPhrase = null;
readonly Action<object> _addAction;
readonly CancellationTokenSource _cancelSource;
public void Cancel()
{
_cancelSource?.Cancel();
}
public async void PerformSearch()
{
await Task.Delay(300); //await 300ms between keystrokes
if (_cancelSource.IsCancellationRequested)
return;
//continue your code keep checking for
//loop your dataset
//call _addAction?.Invoke(uc);
}
}
This is basic, really just encapsulates the logic for both points 1 & 2, you will need to adapt the code to do the search.
Next you could change your property to cancel a previous running instance, and then start another instance immediatly after something like below.
ConnectionSearch connectionSearch;
string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
//do your setter work
if(connectionSearch != null)
{
connectionSearch.Cancel();
}
connectionSearch = new ConnectionSearch(value, addConnectionUser);
connectionSearch.PerformSearch();
}
}
void addConnectionUser(object uc)
{
//pperform your add logic..
}
The code is pretty straight forward, however you will see in the setter is simply cancelling an existing request and then creating a new request. You could put some disposal cleanup logic in place but this should get you started.
You can implement some sort of debouncer which will encapsulate the logics of task result debouncing, i.e. it will assure if you run many tasks, then only the latest task result will be used:
public class TaskDebouncer<TResult>
{
public delegate void TaskDebouncerHandler(TResult result, object sender);
public event TaskDebouncerHandler OnCompleted;
public event TaskDebouncerHandler OnDebounced;
private Task _lastTask;
private object _lock = new object();
public void Run(Task<TResult> task)
{
lock (_lock)
{
_lastTask = task;
}
task.ContinueWith(t =>
{
if (t.IsFaulted)
throw t.Exception;
lock (_lock)
{
if (_lastTask == task)
{
OnCompleted?.Invoke(t.Result, this);
}
else
{
OnDebounced?.Invoke(t.Result, this);
}
}
});
}
public async Task WaitLast()
{
await _lastTask;
}
}
Then, you can just do:
private readonly TaskDebouncer<Connections[]> _connectionsDebouncer = new TaskDebouncer<Connections[]>();
public ClassName()
{
_connectionsDebouncer.OnCompleted += OnConnectionUpdate;
}
public void OnConnectionUpdate(Connections[] connections, object sender)
{
RunOnMainThread(SearchResult.Clear);
isAllFriends = false;
currentPage = 0;
foreach (var conn in connections)
RunOnMainThread(() => SearchResult.Add(new ConnectionUser(conn)));
}
private string searchPhrase;
public string SearchPhrase
{
get => searchPhrase;
set
{
SetProperty(ref searchPhrase, value);
_connectionsDebouncer.Add(RunInAsync(LoadData));
}
}
private async Task<Connection[]> LoadData()
{
return await connectionRepository
.GetConnections(currentPage, pageSize, searchPhrase)
.Where(conn => conn.Type != UserConnection.TypeEnum.Awaiting)
.ToArray();
}
It is not pretty clear what RunInAsync and RunOnMainThread methods are.
I guess, you don't actually need them.
In my Xamarin.Forms pcl project, I have a xaml page with label. I want to update label after some async task. In my ViewModel constructor I set default text for my label. And create a async Task function named SomeTask().
Question 1: Where can I call SomeTask() function. Not able to call async Task function in constructor.
Question 2: How to update Label text after async Task SomeTask() function.
My code:
public class MyPageViewModel : ViewModelBase
{
private String _selectedText;
public String SelectedText
{
get { return _selectedText; }
set {
if (_selectedText != value)
{
_selectedText = value;
}
}
}
public MyPageViewModel ()
{
_selectedText = "Welcome"; //Default text
}
private async Task<string> SomeTask()
{
return await Task.Run(async () =>
{
await Task.Delay(3000); //Dummy task. It will return the status of Task.
return "Thanks"; //Update Text
});
}
}
I recommend that you use my NotifyTask type; it's described in my MSDN article on asynchronous MVVM data binding, and I think it's the easiest approach:
public class MyPageViewModel : ViewModelBase
{
private NotifyTask<string> _selectedText;
public NotifyTask<string> SelectedText => _selectedText;
public MyPageViewModel()
{
_selectedText = NotifyTask.Create(SomeTask(), "Welcome");
}
private async Task<string> SomeTask()
{
await Task.Delay(3000);
return "Thanks";
}
}
Your data binding would then be changed to bind to SelectedText.Result to display "Welcome" followed by "Thanks". There are other NotifyTask<T> properties for data binding, such as IsNotCompleted, IsCompleted, and ErrorMessage, which allows you to handle fault conditions via data binding as well.
If you don't want to use this type, you can do something similar on your own:
public class MyPageViewModel : ViewModelBase
{
private string _selectedText;
public string SelectedText
{
get { return _selectedText; }
set
{
if (_selectedText != value)
{
_selectedText = value;
RaisePropertyNotifyChanged(); // However you're doing this.
}
}
}
public MyPageViewModel()
{
_selectedText = "Welcome";
var _ = RunSomeTask();
}
private async Task RunSomeTask()
{
try
{
SelectedText = await SomeTask();
}
catch (Exception ex)
{
// TODO: Handle the exception.
// It *must* be handled here, or else it will be silently ignored!
}
}
private async Task<string> SomeTask()
{
await Task.Delay(3000);
return "Thanks";
}
}
The constructor starts a RunSomeTask operation and then explicitly ignores its results (note that this means all exceptions will be ignored). The RunSomeTask is responsible for running SomeTask and handling its results (and exceptions). The result is just used to update SelectedText, and exceptions will be handled however you deem appropriate for your app.
How about
public MyPageViewModel()
{
_selectedText = "Welcome"; //Default text
SomeTask().ContinueWith(previousTask => SelectedText = previousTask.Result);
}
You can create an async Factory Method and make you constructor private. Then you call that method to create an instance of MyPageViewModel. Inside that method, you can call string str = await SomeTask
public class MyPageViewModel : ViewModelBase
{
public async MyPageViewModel CreateAsync()
{
var model = new MyPageViewModel();
SelectedText = await SomeTask();
return model;
}
private MyPageViewModel ()
{
_selectedText = "Welcome"; //Default text
}
private Task<string> SomeTask()
{
return Task.Run(async () =>
{
await Task.Delay(3000); //Dummy task. It will return the status of Task.
return "Thanks"; //Update Text
});
}
}
So instead of creating your model like this:
var model = new MyPageViewModel();
You create it like this:
var model = await MyPageViewModel.CreateAsync();
Problem 1:
Use delegate and event.
Create Delegate & associated event:
private delegate void MyDelegate();
private event MyDelegate myEvent;
Subscribe to the event in constructor:
myEvent += async () => await SomeTask();
Execute the event, where ever you need:
myEvent(); //Note: Check the event for null, before executing
Problem 2:
If on a non-UI thread, then:
Use some framework class for executing UI operation: for example - Xamarin provides Device.BeginInvokeOnMainThread
We may always use DataBinding with the Label and only update the value of Binding Path from ViewModel, using event subscription.
string _message;
public string Message
{
get => _message;
set
{
_message = value;
}
}
myEvent += () => Message = "New Value";
<Label Text = "{Binding Message}"/>
I'm trying create a class that has events AND can be awaitable, but keep coming across stumbling blocks.
First, I tried a TransferJob class that returns a TransferTask object which is already running when it is returned. This would be accomplished through something like this:
public abstract class TransferJob
{
public TransferTask Start()
{
return Start(CancellationToken.None);
}
public TransferTask Start(CancellationToken token)
{
TransferTask task = CreateTransferTask();
task.Start(token);
return task;
}
protected abstract TransferTask CreateTransferTask();
}
public abstract class TransferTask
{
public event EventHandler<TransferStatusChangedEventArgs> StatusChanged;
private Task transferTask;
private TransferStatus status;
public TransferStatus Status
{
get { return this.status; }
protected set
{
TransferStatus oldStatus = this.status;
this.status = value;
OnStatusChanged(new TransferStatusChangedEventArgs(oldStatus, value));
}
}
internal void Start(CancellationToken token)
{
this.transferTask = TransferAsync(cancellationToken);
}
protected abstract Task TransferAsync(CancellationToken cancellationToken);
protected virtual void OnStatusChanged(TransferStatusChangedEventArgs txStatusArgs)
{
if (this.StatusChanged != null)
{
this.StatusChanged(this, txStatusArgs);
}
}
public TaskAwaiter GetAwaiter()
{
return this.transferTask.GetAwaiter();
}
}
The problem with the above is that if the TransferTask finishes very quickly, then users of TransferJob.Start() might not have time to register their event handlers on the returned TransferTask's StatusChanged event before it finishes. So I tried a different approach whereby the user has to call the TransferTask's Start() method themselves. This would give the user time to register their event handlers on the TransferTask in between the transferJob.CreateTask() call and the transferTask.Start() call:
public abstract class TransferJob
{
public abstract TransferTask CreateTask();
}
public abstract class TransferTask
{
public event EventHandler<TransferStatusChangedEventArgs> StatusChanged;
private Task transferTask;
private TransferStatus status;
public TransferStatus Status
{
get { return this.status; }
protected set
{
TransferStatus oldStatus = this.status;
this.status = value;
OnStatusChanged(new TransferStatusChangedEventArgs(oldStatus, value));
}
}
public void Start(CancellationToken token)
{
this.transferTask = TransferAsync(cancellationToken);
}
protected abstract Task TransferAsync(CancellationToken cancellationToken);
protected virtual void OnStatusChanged(TransferStatusChangedEventArgs txStatusArgs)
{
if (this.StatusChanged != null)
{
this.StatusChanged(this, txStatusArgs);
}
}
public TaskAwaiter GetAwaiter()
{
return this.transferTask.GetAwaiter();
}
}
Now, I have a different problem. If a user tries await transferTask; before transferTask.Start(); has been called, then presumably they'll get a NullReferenceException thrown because the task hasn't been started (and therefore assigned to the transferTask field). I'm really struggling for a way to solve this. Is there a way? Or a better pattern to use than the above?
I'm not really convinced this is a good idea. Just expose the TAP pattern. Delete the event as well as transferTask. The caller of Start must hold onto that task and pass it to any code that wants to listen for completion. This results in a very clean API. No mutable state, very simple to understand, supports all use cases.
If you insist, you can create a proxy task that looks like it's the real thing:
public abstract class TransferTask
{
public event EventHandler<TransferStatusChangedEventArgs> StatusChanged;
private TaskCompletionSource<object> transferTask = new ...; //changed
private TransferStatus status;
public TransferStatus Status
{
get { return this.status; }
protected set
{
TransferStatus oldStatus = this.status;
this.status = value;
OnStatusChanged(new TransferStatusChangedEventArgs(oldStatus, value));
}
}
public Task Start(CancellationToken token)
{
await TransferAsync(cancellationToken);
transferTask.SetResult(null); //complete proxy task
}
protected abstract Task TransferAsync(CancellationToken cancellationToken);
protected virtual void OnStatusChanged(TransferStatusChangedEventArgs txStatusArgs)
{
if (this.StatusChanged != null)
{
this.StatusChanged(this, txStatusArgs);
}
}
public TaskAwaiter GetAwaiter()
{
return this.transferTask.Task.GetAwaiter(); //changed
}
}
Now, transferTask.Task is always not null. That task will eventually complete. I quickly hacked this together, I hope the idea is clear.
Probably, you should base the event on transferTask.Task.ContinueWith(...).
The best way I found when trying to mix events and awaitable code in C# is to use the Reactive Extension (Rx) library. From Microsoft:
Reactive Extension (Rx) is a library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators.
You could do something like the following to fix your issue. (I am not sure this is exactly what you want to accomplish, but the goal is just to demonstrate how Rx can be used to combine events with asynchronous code):
public async Task TransferAndWaitStartedAsync()
{
var transferTask = new TransferTask();
// Prepare the observable before executing the transfer to make sure that the observable sequence will receive the event
// You can use Linq operators to filter only specific events. In this case, I only care about events with Status == StatusCode.Started
var whenStatusChanged = Observable.FromEventPattern<TransferStatusChangedEventArgs>(h, transferTask.StatusChanged += h, h => transferTask.StatusChanged -= h)
.Where(e => e.EventArgs.Status == StatusCode.Started)
.FirstAsync();
// Start the transfer asynchronously
await transferTask.TransferAsync();
// Continuation will complete when receiving the first event that matches the predicate in the observable sequence even if the event was triggered too quickly.
await whenStatusChanged;
}
I find that the Rx library has a steep learning curve with all its subtleties, but when you know how to use it, it is a really powerful tool.
Intro to Rx with lot of examples
Design guidelines
I have a method that queues some work to be executed asynchronously. I'd like to return some sort of handle to the caller that can be polled, waited on, or used to fetch the return value from the operation, but I can't find a class or interface that's suitable for the task.
BackgroundWorker comes close, but it's geared to the case where the worker has its own dedicated thread, which isn't true in my case. IAsyncResult looks promising, but the provided AsyncResult implementation is also unusable for me. Should I implement IAsyncResult myself?
Clarification:
I have a class that conceptually looks like this:
class AsyncScheduler
{
private List<object> _workList = new List<object>();
private bool _finished = false;
public SomeHandle QueueAsyncWork(object workObject)
{
// simplified for the sake of example
_workList.Add(workObject);
return SomeHandle;
}
private void WorkThread()
{
// simplified for the sake of example
while (!_finished)
{
foreach (object workObject in _workList)
{
if (!workObject.IsFinished)
{
workObject.DoSomeWork();
}
}
Thread.Sleep(1000);
}
}
}
The QueueAsyncWork function pushes a work item onto the polling list for a dedicated work thread, of which there will only over be one. My problem is not with writing the QueueAsyncWork function--that's fine. My question is, what do I return to the caller? What should SomeHandle be?
The existing .Net classes for this are geared towards the situation where the asynchronous operation can be encapsulated in a single method call that returns. That's not the case here--all of the work objects do their work on the same thread, and a complete work operation might span multiple calls to workObject.DoSomeWork(). In this case, what's a reasonable approach for offering the caller some handle for progress notification, completion, and getting the final outcome of the operation?
Yes, implement IAsyncResult (or rather, an extended version of it, to provide for progress reporting).
public class WorkObjectHandle : IAsyncResult, IDisposable
{
private int _percentComplete;
private ManualResetEvent _waitHandle;
public int PercentComplete {
get {return _percentComplete;}
set
{
if (value < 0 || value > 100) throw new InvalidArgumentException("Percent complete should be between 0 and 100");
if (_percentComplete = 100) throw new InvalidOperationException("Already complete");
if (value == 100 && Complete != null) Complete(this, new CompleteArgs(WorkObject));
_percentComplete = value;
}
public IWorkObject WorkObject {get; private set;}
public object AsyncState {get {return WorkObject;}}
public bool IsCompleted {get {return _percentComplete == 100;}}
public event EventHandler<CompleteArgs> Complete; // CompleteArgs in a usual pattern
// you may also want to have Progress event
public bool CompletedSynchronously {get {return false;}}
public WaitHandle
{
get
{
// initialize it lazily
if (_waitHandle == null)
{
ManualResetEvent newWaitHandle = new ManualResetEvent(false);
if (Interlocked.CompareExchange(ref _waitHandle, newWaitHandle, null) != null)
newWaitHandle.Dispose();
}
return _waitHandle;
}
}
public void Dispose()
{
if (_waitHandle != null)
_waitHandle.Dispose();
// dispose _workObject too, if needed
}
public WorkObjectHandle(IWorkObject workObject)
{
WorkObject = workObject;
_percentComplete = 0;
}
}
public class AsyncScheduler
{
private Queue<WorkObjectHandle> _workQueue = new Queue<WorkObjectHandle>();
private bool _finished = false;
public WorkObjectHandle QueueAsyncWork(IWorkObject workObject)
{
var handle = new WorkObjectHandle(workObject);
lock(_workQueue)
{
_workQueue.Enqueue(handle);
}
return handle;
}
private void WorkThread()
{
// simplified for the sake of example
while (!_finished)
{
WorkObjectHandle handle;
lock(_workQueue)
{
if (_workQueue.Count == 0) break;
handle = _workQueue.Dequeue();
}
try
{
var workObject = handle.WorkObject;
// do whatever you want with workObject, set handle.PercentCompleted, etc.
}
finally
{
handle.Dispose();
}
}
}
}
If I understand correctly you have a collection of work objects (IWorkObject) that each complete a task via multiple calls to a DoSomeWork method. When an IWorkObject object has finished its work you'd like to respond to that somehow and during the process you'd like to respond to any reported progress?
In that case I'd suggest you take a slightly different approach. You could take a look at the Parallel Extension framework (blog). Using the framework, you could write something like this:
public void QueueWork(IWorkObject workObject)
{
Task.TaskFactory.StartNew(() =>
{
while (!workObject.Finished)
{
int progress = workObject.DoSomeWork();
DoSomethingWithReportedProgress(workObject, progress);
}
WorkObjectIsFinished(workObject);
});
}
Some things to note:
QueueWork now returns void. The reason for this is that the actions that occur when progress is reported or when the task completes have become part of the thread that executes the work. You could of course return the Task that the factory creates and return that from the method (to enable polling for example).
The progress-reporting and finish-handling are now part of the thread because you should always avoid polling when possible. Polling is more expensive because usually you either poll too frequently (too early) or not often enough (too late). There is no reason you can't report on the progress and finishing of the task from within the thread that is running the task.
The above could also be implemented using the (lower level) ThreadPool.QueueUserWorkItem method.
Using QueueUserWorkItem:
public void QueueWork(IWorkObject workObject)
{
ThreadPool.QueueUserWorkItem(() =>
{
while (!workObject.Finished)
{
int progress = workObject.DoSomeWork();
DoSomethingWithReportedProgress(workObject, progress);
}
WorkObjectIsFinished(workObject);
});
}
The WorkObject class can contain the properties that need to be tracked.
public class WorkObject
{
public PercentComplete { get; private set; }
public IsFinished { get; private set; }
public void DoSomeWork()
{
// work done here
this.PercentComplete = 50;
// some more work done here
this.PercentComplete = 100;
this.IsFinished = true;
}
}
Then in your example:
Change the collection from a List to a Dictionary that can hold Guid values (or any other means of uniquely identifying the value).
Expose the correct WorkObject's properties by having the caller pass the Guid that it received from QueueAsyncWork.
I'm assuming that you'll start WorkThread asynchronously (albeit, the only asynchronous thread); plus, you'll have to make retrieving the dictionary values and WorkObject properties thread-safe.
private Dictionary<Guid, WorkObject> _workList =
new Dictionary<Guid, WorkObject>();
private bool _finished = false;
public Guid QueueAsyncWork(WorkObject workObject)
{
Guid guid = Guid.NewGuid();
// simplified for the sake of example
_workList.Add(guid, workObject);
return guid;
}
private void WorkThread()
{
// simplified for the sake of example
while (!_finished)
{
foreach (WorkObject workObject in _workList)
{
if (!workObject.IsFinished)
{
workObject.DoSomeWork();
}
}
Thread.Sleep(1000);
}
}
// an example of getting the WorkObject's property
public int GetPercentComplete(Guid guid)
{
WorkObject workObject = null;
if (!_workList.TryGetValue(guid, out workObject)
throw new Exception("Unable to find Guid");
return workObject.PercentComplete;
}
The simplest way to do this is described here. Suppose you have a method string DoSomeWork(int). You then create a delegate of the correct type, for example:
Func<int, string> myDelegate = DoSomeWork;
Then you call the BeginInvoke method on the delegate:
int parameter = 10;
myDelegate.BeginInvoke(parameter, Callback, null);
The Callback delegate will be called once your asynchronous call has completed. You can define this method as follows:
void Callback(IAsyncResult result)
{
var asyncResult = (AsyncResult) result;
var #delegate = (Func<int, string>) asyncResult.AsyncDelegate;
string methodReturnValue = #delegate.EndInvoke(result);
}
Using the described scenario, you can also poll for results or wait on them. Take a look at the url I provided for more info.
Regards,
Ronald
If you don't want to use async callbacks, you can use an explicit WaitHandle, such as a ManualResetEvent:
public abstract class WorkObject : IDispose
{
ManualResetEvent _waitHandle = new ManualResetEvent(false);
public void DoSomeWork()
{
try
{
this.DoSomeWorkOverride();
}
finally
{
_waitHandle.Set();
}
}
protected abstract DoSomeWorkOverride();
public void WaitForCompletion()
{
_waitHandle.WaitOne();
}
public void Dispose()
{
_waitHandle.Dispose();
}
}
And in your code you could say
using (var workObject = new SomeConcreteWorkObject())
{
asyncScheduler.QueueAsyncWork(workObject);
workObject.WaitForCompletion();
}
Don't forget to call Dispose on your workObject though.
You can always use alternate implementations which create a wrapper like this for every work object, and who call _waitHandle.Dispose() in WaitForCompletion(), you can lazily instantiate the wait handle (careful: race conditions ahead), etc. (That's pretty much what BeginInvoke does for delegates.)