External Function Call Blocks UI thread - c#

I am working on an application that talks to a motion controller over ethernet.
To connect to the controller I use a library provided by the supplier, to connect you create an instance of the controller than then tell it to connect, this has the chance to block for a few seconds (with no controllable timeout) if there is no controller present. This cause freeze ups in the UI.
To avoid this I thought I would be able to use Tasks to run the connection in a different thread.
ConnectionTask = Task.Factory.StartNew(() =>
{
try
{
RMCLink rmc = RMCLink.CreateEthernetLink(DeviceType.RMC70, "192.168.0.55");
RMC.Connect();
}
catch
{
this.logger.Log("Failed to connect");
}
}, TaskCreationOptions.LongRunning);
This has no effect whatsoever and the UI still locks up.
I think I am using them properly as if I replace it with the below code the UI is fine even though the separate thread takes a few seconds before the message comes out.
ConnectionTask = Task.Factory.StartNew(() =>
{
int x = 1;
while (x != 0) x++;
this.logger.Log("Failed to connect");
}, TaskCreationOptions.LongRunning);
Is there any way I can identify what is going on and prevent calls that I do not know anything about their inner workings from locking the UI thread.

Use async/await, something along the lines of:
public async void MyButton_Click(object sender, EventArgs e)
{
await CreateEthernetLink();
this.logger.Log("Connected!");
}
private async Task CreateEthernetLink()
{
var task = Task.Run(() => {
try
{
RMCLink rmc = RMCLink.CreateEthernetLink(DeviceType.RMC70, "192.168.0.55");
rmc.Connect();
}
catch
{
this.logger.Log("Failed to connect");
}});
await task;
}
The await will capture the current thread (or SynchronizationContext - in this case the UI thread which is being blocked) and restore it after the async work has been completed.
So the threading is all handled for you behind the scenes and you should notice no difference in your application other than the fact that your application no longer freezes when performing connections.
EDIT: I also noticed in your code your initializing rmc but calling connect on RMC. I don't think this is correct.

Related

.net MAUI c# Background Task ContinueWith and notification Event

[EDIT]Solved, see below[/EDIT]
this is a newbie-question.
I'm just digging in to c# and async and whyt i would like to have:
click Button
run several tasks in order but in background-thread, one after another
running tasks should notifiy their progress if possible
right now i can click the botton and start the task-chain, but within the completition event i would like (for testing) show a message-box every time a task has finished. this may lead to a crash (?) and i don't know why since i thought i would be within the ui-thread ...
here are some parts of the code:
AppViewModel:
void handlePhaseCompletedEvent(object sender, SyncPhaseCompletedEventArgs e)
{
Shell.Current.DisplayAlert("TEST", "PHASE " + e.phase.ToString(), "OK"); // <<<< doesn't show up, maybe because its crashing a short time after?
syncToolService.StartSyncPhaseAsync(e.phase + 1, this); // <<<< seems to crash here?
}
[RelayCommand]
async Task StartSyncAsync()
{
syncToolService.NotifySyncPhaseCompleted += handlePhaseCompletedEvent;
syncToolService.StartSyncPhaseAsync(0, this);
}
syncToolService:
public event EventHandler<SyncPhaseCompletedEventArgs> NotifySyncPhaseCompleted;
public async Task StartSyncPhaseAsync(int phase, AppViewModel viewModel)
{
// search for Remote-peer
if (phase == 0)
{
Task t = new Task(() => Task.Delay(100)); // dummy, not implemented yet
t.ConfigureAwait(false);
t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
t.Start();
return;
}
// Remote Sync start preparations
if (phase == 1)
{
Task t = new Task(() => Task.Delay(100)); // dummy, not implemented yet
t.ConfigureAwait(false);
t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
t.Start();
return;
}
//////// LOCAL PREPARATIONS
// read local files
if (phase == 2)
{
Task t = new Task(() => BPMSyncToolService.loadLocalData(viewModel.DataFiles));
t.ConfigureAwait(false);
t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
t.Start();
return;
}
}
basicly i thought StartSyncPhaseAsync would run a Task (and it seems to do so)
and it also seems to trigger the event (whicht seems not to raise the exeption)
when running line by line in debug it crashes after syncToolService.StartSyncPhaseAsync(e.phase + 1, this);
with this stack:
> [Exception] WinRT.Runtime.dll!WinRT.ExceptionHelpers.ThrowExceptionForHR.__Throw|20_0(int hr)
[Exception] Microsoft.WinUI.dll!Microsoft.UI.Xaml.Controls.ContentDialog._IContentDialogFactory.CreateInstance(object baseInterface, out System.IntPtr innerInterface)
[Exception] Microsoft.WinUI.dll!Microsoft.UI.Xaml.Controls.ContentDialog.ContentDialog()
[Exception] Microsoft.Maui.Controls.dll!Microsoft.Maui.Controls.Platform.AlertManager.AlertRequestHelper.OnAlertRequested(Microsoft.Maui.Controls.Page sender, Microsoft.Maui.Controls.Internals.AlertArguments arguments)
System.Private.CoreLib.dll!System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Private.CoreLib.dll!System.Threading.Tasks.Task.ThrowAsync.AnonymousMethod__128_1(object state)
System.Private.CoreLib.dll!System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
i also may have a general problem in my design, any help would be great!
[UPDATE]
it runs now as expected.
newbie-thoughts:
the answer from ToolmakerSteve https://stackoverflow.com/a/73409415/4232410 i thought "hey, but thats what i tried first and it locked UI". Then i've watched https://www.youtube.com/watch?v=2moh18sh5p4&t=0s and https://www.youtube.com/watch?v=ZTKGRJy5P2M and i saw "hey, its basicly what was mentioned and there it works, so where is my fault (now as i'm writing this i saw his update, thanks alot!)
Ryan mentioned "ReportProgress" (that was the way i stumbled across the above videos), and it worked, also thank you!
so this is basicly the actual working code that seems NOT to lock the UI and doesn't crash (the crash was because of Microsoft.VisualBasic.FileIO.TextFieldParser that tried to read a line and found a field beginning with a quote and thought it would be an enclosing quote which it wasn't)
AppViewModel:
private void HandleSyncProgressChanged(object sender, SyncStatus e)
{
NumFilesProcessed = e.filesProcessed;
NumFilesNotFound = e.filesNotFound;
AktueleAufgabe = e.workingPhase;
}
[RelayCommand]
async Task StartSyncAsync()
{
Progress<SyncStatus> progress=new Progress<SyncStatus>();
progress.ProgressChanged += HandleSyncProgressChanged;
await BPMSyncToolService.StartSyncPhaseAsync(this, progress);
}
syncToolService:
public static async Task StartSyncPhaseAsync(AppViewModel viewModel, IProgress<SyncStatus> progress)
{
SyncStatus report = new SyncStatus();
report.workingPhase = "Suche Synchronisationspartner";
progress.Report(report);
// search for Remote-peer
await Task.Delay(100); // dummy, not implemented yet
report.workingPhase = "Starte Vorbereitungen beim Synchronisationspartner";
progress.Report(report);
// Remote Sync start preparations
await Task.Delay(100); // dummy, not implemented yet
//////// LOCAL PREPARATIONS
report.workingPhase = "lese lokale Dateien";
progress.Report(report);
// read local files
await BPMSyncToolService.LoadLocalDataAsync(viewModel.DataFiles, progress, report);
// [...]
}
what i actually can't see is the counting up of processed files, maybe it's too fast, don't know, will see in further tasks that will require more time
anyways, thanks, both answers helped, i will mark the one as solution, that was closer to the core problem (i think)
Given async/await, it is almost never necessary to use task continuations or ConfigureAwait.
To start a sequence in the background, wrap the sequence in Task.Run.
To report progress on UI thread, use Dispatcher.Dispatch.
Example:
// IMPORTANT: `await`.
// Otherwise, current method would continue before Task.Run completes.
await Task.Run(async () =>
{
// Now on background thread.
...
// Report progress to UI.
Dispatcher.Dispatch(() =>
{
// Code here is queued to run on MainThread.
// Assuming you don't need to wait for the result,
// don't need await/async here.
}
// Still on background thread.
...
};
// This is effectively the "continuation": Code here runs after Task.Run completes.
...
UPDATE
In response to comment, this is how you use async/await to start a sequence of tasks, without waiting for the result:
If your top-level code does UI calls:
// This queues an independent execution to MainThread.
// We don't "await" the Dispatch, because we want it to run independently.
Dispatcher.Dispatch(async () => await TopMethod());
If your top-level code does not do UI calls:
// This queues an independent execution to the Thread Pool.
// We don't "await" the Run, because we want it to run independently.
Task.Run(async () => await TopMethod());
In either case, instead of using continuations, TopMethod uses awaits to sequence the tasks:
async void TopMethod()
{
await ..Task1..;
await ..Task2..;
await ..Task3..;
}
This is equivalent to Task1.ContinueWith(Task2.ContinueWith(Task3));
(Off the top of my head; I may not have the syntax quite right on this.)
If you are on a background thread (did Task.Run), then to do UI calls, simply wrap in Dispatcher.Dispatch( ... ). As shown in first code snippet.
You can capture SynchronizationContext in your syncToolService in constructor, or by defining explicitly API for capturing, kinda:
public void CaptureSynchronizationContext(SynchronizationContext context)
{
var current = SynchronizationContext.Current;
if (context is null)
{
this.capturedScheduler = TaskScheduler.Current;
return;
}
SynchronizationContext.SetSynchronizationContext(context);
this.capturedScheduler = TaskScheduler.FromCurrentSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(current);
}
Add make some wrapper for your logic to be called in specified context:
private void RunTaskWithContinuation(Task task, Action<Task> continuation)
{
task.ConfigureAwait(false);
task.ContinueWith(t => continuation(t), capturedScheduler);
task.Start();
}
So, somewhere in your UI:
// afaik you should call it once per every Window
syncToolService.CaptureSynchronizationContext(SynchronizationContext.Current);
And your code above would look like this:
// read local files
if (phase == 2)
{
Task t = new Task(() => BPMSyncToolService.loadLocalData(viewModel.DataFiles));
RunTaskWithContinuation(t, () => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
}
Not tested, but i would try this idea first.
Btw, if SynchronizationContext is null, guess your problem would be persisted.
There is space for refactoring, just wanted to show the idea.
UPDATE
There is ReportProgress type - right tool for reports in multithreaded environment. May be this is what you are looking for.
But it works the same way, as i did above - via context capturing.

Using IProgress when reporting progress for async await code vs progress bar control

private static async Task FuncAsync(DataTable dt, DataRow dr)
{
try
{
await Task.Delay(3000); //assume this is an async http post request that takes 3 seconds to respond
Thread.Sleep(1000) //assume this is some synchronous code that takes 2 second
}
catch (Exception e)
{
Thread.Sleep(1000); //assume this is synchronous code that takes 1 second
}
}
private async void Button1_Click(object sender, EventArgs e)
{
List<Task> lstTasks = new List<Task>();
DataTable dt = (DataTable)gridview1.DataSource;
foreach (DataRow dr in dt.Rows)
{
lstTasks.Add(FuncAsync(dr["colname"].ToString());
}
while (lstTasks.Any())
{
Task finishedTask = await Task.WhenAny(lstTasks);
lstTasks.Remove(finishedTask);
await finishedTask;
progressbar1.ReportProgress();
}
}
Assuming the datatable has got 10000 rows.
In the code, on button click, at the 1st iteration of the for loop, an async api request is made. While it takes 3 seconds, the control immediately goes to the caller. So the for loop can make the next iteration, and so on.
When the api response arrives, the code below the await runs as a callback. Thus blocking the UI thread and any incomplete for loop iterations will be delayed until the callback completes irrespective of whether I use await WhenAny or WhenAll.
All code runs on the UI thread due to the presence of synchronization context. I can do ConfigureAwait false on Task.Delay so the callbacks run on separate threads in order to unblock the ui thread.
Say 1000 iterations are made when the 1st await returns and when the 1st iterations await call back runs the following iterations will have completed completed awaits so their callbacks will run. Effectively callbacks will run one after the other if configure await is true. If false then they will run in parallel on separate threads.
So I think that the progress bar that I am updating in the while loop is incorrect because - by the time the code reaches the while block, most of the initial for loop iterations will have been already completed. I hope that I have understood correctly so far.
I have the following options to report progress from inside the task:
using IProgress (I think this is more suitable to report progress from another thread [for example when using Task.Run], or in usual async await if the configure await is false, resulting in code below the await to run in separate thread otherwise it will not show the progress bar moving as the ui thread will be blocked running the callbacks. In my current example code always runs on the same UI thread). So I was thinking the below point may be more appropriate solution.
making the Task non-static so that I can access the progress bar from within the Task and do porgressbar1.PerformStep().
Another thing I have noticed is that await WhenAll doesn't guarantee that IProgress is fully executed.
The IProgress<T> implementation offered natively by the .NET platform, the Progress<T> class, has the interesting characteristic of notifying the captured SynchronizationContext asynchronously, by invoking its Post method. This characteristic sometimes results to unexpected behavior. For example can you guess what effect has the code below to the Label1 control?
IProgress<string> progress = new Progress<string>(s => Label1.Text = s);
progress.Report("Hello");
Label1.Text = "World";
What text will be eventually written to the label, "Hello" or "World"? The correct answer is: "Hello". The delegate s => Label1.Text = s is invoked asynchronously, so it runs after the execution of the Label1.Text = "World" line, which is invoked synchronously.
Implementing a synchronous version of the Progress<T> class is quite trivial. All you have to do is copy-paste Microsoft's source code, rename the class from Progress<T> to SynchronousProgress<T>, and change the line m_synchronizationContext.Post(... to m_synchronizationContext.Send(.... This way every time you invoke the progress.Report method, the call will block until the invocation of the delegate on the UI thread is completed. The unfortunate implication of this is that if the UI thread is blocked for some reason, for example because you used the .Wait() or the .Result to wait synchronously for the task to complete, your application will deadlock.
The asynchronous nature of the Progress<T> class is rarely a problem in practice, but if you want to avoid thinking about it you can just manipulate the ProgressBar1 control directly. After all you are not writing a library, you are just writing code in the event handler of a button to make some HTTP requests. My suggestion is to forget about the .ConfigureAwait(false) hackery, and just let the main workflow of your asynchronous event handler to stay on the UI thread from start to end. If you have synchronous blocking code that needs to be offloaded to a ThreadPool thread, use the Task.Run method to offload it. To create your tasks, instead of manually adding tasks to a List<Task>, use the handly LINQ Select operator to project each DataRow to a Task. Also add a reference to the System.Data.DataSetExtensions assembly, so that the DataTable.AsEnumerable extension method becomes available. Finally add a throttler (a SemaphoreSlim), so that your application makes efficient use of the available network bandwidth, and it doesn't overburden the target machine:
private async void Button1_Click(object sender, EventArgs e)
{
Button1.Enabled = false;
const int maximumConcurrency = 10;
var throttler = new SemaphoreSlim(maximumConcurrency, maximumConcurrency);
DataTable dataTable = (DataTable)GridView1.DataSource;
ProgressBar1.Minimum = 0;
ProgressBar1.Maximum = dataTable.Rows.Count;
ProgressBar1.Step = 1;
ProgressBar1.Value = 0;
Task[] tasks = dataTable.AsEnumerable().Select(async row =>
{
await throttler.WaitAsync();
try
{
await Task.Delay(3000); // Simulate an asynchronous HTTP request
await Task.Run(() => Thread.Sleep(2000)); // Simulate synchronous code
}
catch
{
await Task.Run(() => Thread.Sleep(1000)); // Simulate synchronous code
}
finally
{
throttler.Release();
}
ProgressBar1.PerformStep();
}).ToArray();
await Task.WhenAll(tasks);
Button1.Enabled = true;
}
You can simply add a wrapper function:
private IProgress<double> _progress;
private int _jobsFinished = 0;
private int _totalJobs = 1000;
private static async Task FuncAsync()
{
try
{
await Task.Delay(3000); //assume this is an async http post request that takes 3 seconds to respond
Thread.Sleep(1000); //assume this is some synchronous code that takes 2 second
}
catch (Exception e)
{
Thread.Sleep(1000); //assume this is synchronous code that takes 1 second
}
}
private async Task AwaitAndUpdateProgress()
{
await FuncAsync(); // Can also do Task.Run(FuncAsync) to run on a worker thread
_jobsFinished++;
_progress.Report((double) _jobsFinished / _totalJobs);
}
And then just WhenAll after adding all the calls.

Task not working if background method needs UI: The calling thread must be STA, because many UI components require this

I have one Async method which performs different operations.
public async void OnClickPublish( )
{
Loader loader = new Loader();
loader.Show();
await Task.Run(() => PublishSlides(loader));
}
private async Task PublishSlides(Loader loader)
{
await loader.Dispatcher.Invoke(async () =>
{
loader.LoaderMessage("Opration 1 start..");
List<SlideProperties> DBList= await Task.Run(() =>
objSlideImg.DBOpration()); //UI Not needed. work nice
var cloudTask = SendToCloudAsync(DBList);
await cloudTask.ContinueWith(task =>
{
if (task.IsFaulted)
{
loader.LoaderMessage(task.Exception.Message + " problem occur in cloud publish");
return;
}
loader.ShowSuccess("broadcasting..");
}, uiScheduler);
}
}
/*PROBLEM OCCUR IN THIS METHOD*/
public async Task<bool> SendToCloudAsync(List<SlideProperties> DBList)
{
**DashboardUI dashboard= new DashboardUI();** /* giving issue The calling thread must be STA, because many UI components require this.*/
dashboard.ShowDashboard()
}
So when I called the SendToCloudAsync() methods it will give the issue The calling thread must be STA, because many UI components require this. SendToCloudAsync() this method will show the dashboard of my APP.
I strongly recommend separating UI code from background code, and having the UI layer "drive" the business/background code.
Currently, OnClickPublish calls PublishSlides on a background thread (Task.Run), and then PublishSlides immediately jumps back to the UI thread (Dispatcher.Invoke) and runs its entire method body on the UI thread. This isn't accomplishing anything - just jumping back and forth across threads.
To properly divide UI from background thread code, there are two primary techniques and one less common technique.
The first primary technique is to use return values. Instead of having a method retrieve/calculate data and then update the UI with the data results, just have it retrieve/calculate data and return it. Then the calling thread can decide whether the retrieving/calculation should be on a background thread (Task.Run), and then place the data on the UI.
The second primary technique is to use progress updates. Instead of having a "work" method reach into the UI and update the progress directly, it should use IProgress<T> to report progress. Then the calling method can decide whether the "work" should be run on a background thread (Task.Run), and it can pass a progress implementation (e.g., Progress<T>) that performs progress updates on the UI thread.
The less common technique is to proactively update the UI from a continuously running background operation. I recommend using SynchronizationContext for that, but because the code is more complex and doesn't apply to this question I'll just stop there.
Here's one example of what your code could look like using return values and await. The exact details will depend on the details of the rest of your code:
public async void OnClickPublish()
{
Loader loader = new Loader();
loader.Show();
PublishSlides(loader);
}
private async Task PublishSlides(Loader loader)
{
loader.LoaderMessage("Opration 1 start..");
List<SlideProperties> DBList = await Task.Run(() => objSlideImg.DBOpration());
try
{
await SendToCloudAsync(DBList);
loader.ShowSuccess("broadcasting..");
}
catch (Exception ex)
{
loader.LoaderMessage(ex.Message + " problem occur in cloud publish");
}
DashboardUI dashboard = new DashboardUI();
dashboard.ShowDashboard();
}
public async Task<bool> SendToCloudAsync(List<SlideProperties> DBList)
{
...
}

How to track if an async/awaitable task is running

I'm trying to transition from the Event-based Asynchronous Pattern where I tracked running methods using unique id's and the asynoperationmanager. As this has now been dropped from Windows 8 Apps I'm trying to get a similar effect with Async/Await but can't quite figure out how.
What I'm trying to achieve is something like
private async Task updateSomething()
{
if(***the method is already running***)
{
runagain = true;
}
else
{
await someMethod();
if (runagain)
{
run the method again
}
}
}
The part I'm struggling with is finding out if the method is running. I've tried creating a Task and looking at the status of both that and the .status of the async method but they don't appear to be the correct place to look.
Thanks
UPDATE: This is the current code I use in .net 4 to achieve the same result. _updateMetaDataAsync is a class based on the Event-Based Asynchronous Pattern.
private void updateMetaData()
{
if (_updateMetaDataAsync.IsTaskRunning(_updateMetaDataGuid_CheckAllFiles))
{
_updateMetaDataGuid_CheckAllFiles_Again = true;
}
else
{
_updateMetaDataGuid_CheckAllFiles_Again = false;
_updateMetaDataAsync.UpdateMetaDataAsync(_updateMetaDataGuid_CheckAllFiles);
}
}
private void updateMetaDataCompleted(object sender, UpdateMetaDataCompletedEventArgs e)
{
if (_updateMetaDataGuid_CheckAllFiles_Again)
{
updateMetaData();
}
}
async/await itself is intended to be used to create sequential operations executed asynchronously from the UI thread. You can get it to do parallel operations, but generally the operations "join" back to the UI thread with some sort of result. (there's also the possibility of doing "fire-and-forget" types of asynchronous operations with await but it's not recommended). i.e. there's nothing inherent to async/await to support progress reporting.
You can get progress out of code using async/await; but you need to use new progress interfaces like IProgress<T>. For more info on progress reporting with async/await, see http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx. Migrating to this should just be a matter of calling an IProgress delegate instead of a Progress event.
If you're using a Task you've created, you can check the Task's Status property (or just see Task.IsCompleted if completion is the only state you are interested in).
That being said, await will not "return" until the operation either completes, raises an exception, or cancels. You can basically safely assume that, if you're still waiting on the "await", your task hasn't completed.
SemaphoreSlim queueToAccessQueue = new SemaphoreSlim(1);
object queueLock = new object();
long queuedRequests = 0;
Task _loadingTask;
public void RetrieveItems() {
lock (queueLock) {
queuedRequests++;
if (queuedRequests == 1) { // 1 is the minimum size of the queue before another instance is queued
_loadingTask = _loadingTask?.ContinueWith(async () => {
RunTheMethodAgain();
await queueToAccessQueue.WaitAsync();
queuedRequests = 0; // indicates that the queue has been cleared;
queueToAccessQueue.Release()
}) ?? Task.Run(async () => {
RunTheMethodAgain();
await queueToAccessQueue.WaitAsync();
queuedRequests = 0; // indicates that the queue has been cleared;
queueToAccessQueue.Release();
});
}
}
}
public void RunTheMethodAgain() {
** run the method again **
}
The added bonus is that you can see how many items are sitting in the queue!

Why would a long running Task still block the UI?

I'm trying to resolve a problem where my UI is being blocked and I don't understand why.
public Task AddStuff(string myID, List<string> otherIDs)
{
Action doIt = () =>
{
this.theService.AddStuff(myID, otherIDs);
};
return Task.Factory.StartNew(doIt, TaskCreationOptions.LongRunning);
}
If the list is long the call can take 30 seconds and the entire application becomes unresponsive (goes that washed out white in Windows 7).
Is there a different way to do this so it doesn't block the UI?
Edit
Ok, so there's a LOT of code around this I'm going to try to keep this pertinent. I did realize going back to the original code, that I had removed something that may have been important. Should I maybe use a different TaskScheduler than TaskScheduler.Current?
Also there are no Wait statements impeding any of this code, and the service doesn't interact with the UI.
Task.Factory.StartNew(objState =>
{
LoadAssets(objState);
}, state, this.cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
private void LoadAssets(object objState)
{
LoadAssetsState laState = (LoadAssetsState)objState;
List<string> assetIDs = new List<string>();
for (int i = 0; i < laState.AddedMediaItems.Count; i++)
{
if (laState.CancellationToken.IsCancellationRequested)
return;
string assetId = this.SelectFilesStep.AssetService.GetAssetId(laState.AddedMediaItems[i], laState.ActiveOrder.OrderID);
assetIDs.Add(assetId);
}
if (laState.CancellationToken.IsCancellationRequested)
return;
this.ApiContext.AddAssetToProduct(laState.ActiveOrder.OrderID, laState.ActiveProduct.LineID, assetIDs, laState.Quantity, laState.CancellationToken).ContinueWith(task =>
{
if (laState.CancellationToken.IsCancellationRequested)
return;
App.ApiContext.GetOrderDetails(laState.ActiveOrder.OrderID, false, laState.CancellationToken).ContinueWith(orderDetailsTask =>
{
if (laState.CancellationToken.IsCancellationRequested)
return;
this.activeOrder = orderDetailsTask.Result;
this.StandardPrintProductsStep.Synchronize(this.activeOrder);
});
});
}
public Task AddAssetToProduct(string orderID, string lineID, List<string> assetIDs, int quantity, CancellationToken? cancellationToken = null)
{
Action doIt = () =>
{
if (cancellationToken.IsCancellationRequested())
return;
this.ordersService.AddAssetToProduct(orderID, lineID, assetIDs, quantity);
};
if (cancellationToken != null)
return Task.Factory.StartNew(doIt, cancellationToken.Value, TaskCreationOptions.LongRunning, TaskScheduler.Current);
else
return Task.Factory.StartNew(doIt, TaskCreationOptions.LongRunning);
}
EDIT
I have placed break points just before and after the service call and it is the service call that is blocking the UI, as opposed to any other line.
It sounds like there is no reason this should be blocking, so I think I'm just going to break the list down if it's long and make multiple calls. I just wanted to make sure I wasn't missing something here with my Task logic.
Is there a different way to do this so it doesn't block the UI?
This call, in and of itself, should not block the UI. If, however, theService.AddStuff does some synchronization with the UI's SynchronizationContext, this could cause the UI to effectively be blocked by that call.
Otherwise, the problem is likely happening from outside of this function. For example, if you call Wait() on the task returned from this method, in a UI thread, the UI thread will be blocked until this completes.
You probably want to use TaskScheduler.Default, not TaskScheduler.Current. If this is being called within a Task that's scheduled on a TaskScheduler based on the UI thread, it will schedule itself on the UI thread.
Wish I could put formatted code in a comment, but since I don't see how, adding this snippet as an answer. This is the kind of approach I'd use to figure out whether the task is running on the UI thread or not (since you don't want it to) and have the action be something completely different (a simple thread.sleep).
var state = new object();
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var task = Task.Factory.StartNew(
objState => { Console.WriteLine ("Current thread is {0}", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(30); },
state,
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Current);
task.Wait();

Categories