IProgress<T> does not fire events in current context - c#

I've managed to implement a Task in my class UpdateManager that downloads a file from my webspace.
public async Task DownloadPackageTask(IProgress<int> progress)
{
var webRequest = WebRequest.Create(new Uri("http://www.mywebspace.de/archive.zip"));
using (var webResponse = await webRequest.GetResponseAsync())
{
var buffer = new byte[1024];
FileStream fileStream = File.Create("Path"));
using (Stream input = webResponse.GetResponseStream())
{
int received = 0;
double total = 0d;
var size = GetFileSize(new Uri("...")); // Gets the content length with a request as the Stream.Length-property throws an NotSupportedException
if (size != null)
total = (long) size.Value;
int size = await input.ReadAsync(buffer, 0, buffer.Length);
while (size > 0)
{
fileStream.Write(buffer, 0, size);
received += size;
progress.Report((received/total)*100));
size = await input.ReadAsync(buffer, 0, buffer.Length);
}
}
}
}
This works well, the file is being downloaded and also if I add Debug.Print((received/total)*100) it outputs the correct percentage, everything is alright. The method is marked as async so that it can be awaited/wrapped asynchronously in a task.
The problem occurs in another class UpdaterUi that is basically the interface between the manager and the user interface and calls the method like that:
public void ShowUserInterface()
{
TaskEx.Run(async delegate
{
var downloadDialog = new UpdateDownloadDialog
{
LanguageName = _updateManager.LanguageCulture.Name,
PackagesCount = _updateManager.PackageConfigurations.Count()
};
_context.Post(downloadDialog.ShowModalDialog, null); // Do this with a SynchronizationContext as we are not on the UI thread.
var progressIndicator = new Progress<int>();
progressIndicator.ProgressChanged += (sender, value) =>
downloadDialog.Progress = value;
await TaskEx.Run(() => _updateManager.DownloadPackageTask(progressIndicator));
});
}
It never calls the anonymous method there that should be invoked as soon as the progress changes, but nothing happens, I debugged it with breakpoints.
The problem is maybe that the progressIndicator is not created on the UI-thread, but on the new thread created by TaskEx.Run. It doesn't fire the event and consecutively the UI does not update the progressbar it contains (which it does in the setter of the Progress-property that is initialized above).
The problem is that I don't know what to do in order to make it working, how am I supposed to change the implementation in my project to get it working, is my thought of that threading problem correct?
Thanks in advance!

Your speculation about the problem is right. Progress<T> should be created in UI thread in order to be notified in UI thread.
It works by capturing the SynchronizationContext and posting the delegate for execution in the captured context. Since it is created in non UI context(default context or threadpool context), it will raise ProgressChanged in threadpool thread.
If you move this line var progressIndicator = new Progress<int>(); out of the TaskEx.Run, it should work (if ShowUserInterface is called from UI thread).
I see you're creating UpdateDownloadDialog in a worker thread. You shouldn't do that. Move that also to UI thread.

Related

xamarin scrollview jumping when adding children

I have a <ScrollView/> which contains a <Grid/> (of <images/>), when the user nears the bottom of the scrollview I connect to a website and download the next set of images (actually, JSON's containing links for ImageSource's) which creates an "endless" scroll box of images.
my issue is that when i download the next set of images the app momentarily hangs and the scrollbox then jumps to catch up as the new set is added. how can i prevent this "Jumping"?
private async void OnScrolled(object sender, ScrolledEventArgs e)
{
ScrollView scroller = (ScrollView)sender;
//threshhold == bottom of scrollveiw + height of one image (aka just before it's visible)
double threashold = (e.ScrollY + scroller.Height) + preview_size;
//if we touch the threshhold...
if (threashold > scroller.ContentSize.Height)
{
//one row of images
int TilePreload = (Tiles.Count + ColCount);
//if the next row exceeds the total available post count, download and append more posts
if (TilePreload >= Posts.Count)
{
//we have reached the end of our postlist, we must get more!
var results = await Task.Run(()=>FetchResults<List<CPost>>());
Posts.AddRange( results);
}
//then, add the tiles to UI
//AddRow();// <- jumpy
//calling this as a task results in no tiles added, and eventually an execption
await Task.Run( () => AddRow() );
}
}
//seperated the for loop as function so it can be ran as a task (if required)
public void AddRow()
{
for (int i = 0; i < RowCount; i++)
{
//wrapper for assigning Image to Grid
//aka ImageSourec = some URL
AddTile(i);
}
}
note: FetchResults<T>(); is more or less a wrapper for
//fyi using System.Net.Http;
public static string GetResponse(string page, Dictionary<String, String> arguments, bool IsPost = false)
{
HttpMethod Method = IsPost ? HttpMethod.Post : HttpMethod.Get;
var request = new HttpRequestMessage(Method, page)
{
Content = new FormUrlEncodedContent(arguments)
};
HttpResponseMessage httpResponse = Client.SendAsync(request).Result;
if (httpResponse.IsSuccessStatusCode)
{
return httpResponse.Content.ReadAsStringAsync().Result;
}
return null;
}
If you are updating the UI, that needs to be done on the main UI thread.
When you call
await Task.Run( () => AddRow() );
That means that AddRow (and any methods it calls) are not running on the UI thread and will cause a crash.
What you might try is something like this (not tested):
private async void OnScrolled(object sender, ScrolledEventArgs e)
{
await Task.Run(async () =>
{
ScrollView scroller = (ScrollView)sender;
//threshhold == bottom of scrollveiw + height of one image (aka just before it's visible)
double threashold = (e.ScrollY + scroller.Height) + preview_size;
//if we touch the threshhold...
if (threashold > scroller.ContentSize.Height)
{
//one row of images
int TilePreload = (Tiles.Count + ColCount);
//if the next row exceeds the total available post count, download and append more posts
if (TilePreload >= Posts.Count)
{
//we have reached the end of our postlist, we must get more!
var results = await Task.Run(()=>FetchResults<List<CPost>>()).ConfigureAwait(false);
Posts.AddRange( results);
}
}
});
//then, add the tiles to UI
AddRow();
}
Also, Why isn't GetResponse an async method?? (using .Result blocks the thread) And thus why isn't FetchResults>() not async?
To make GetResponse async:
public static async Task<string> GetResponse(string page, Dictionary<String, String> arguments, bool IsPost = false)
{
HttpMethod Method = IsPost ? HttpMethod.Post : HttpMethod.Get;
var request = new HttpRequestMessage(Method, page)
{
Content = new FormUrlEncodedContent(arguments)
};
HttpResponseMessage httpResponse = await Client.SendAsync(request).ConfigureAwait(false);
if (httpResponse.IsSuccessStatusCode)
{
return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
}
return null;
}
The way you had it, you had a lot of thread jumping around and code blocking threads due to use of .Result
So by putting all of the code that does not require running on the UI thread inside a Task, you can avoid any code running on the UI thread until you need it to, i.e. when you add the UI elements.
Using .ConfigureAwait(false) means when that task ends, the following code will not be marshaled back to the calling thread, saving some thread marshalling, which takes time. When .ConfigureAwait(false) is not called the default is .ConfigureAwait(true) which means "When this task is done, marshal the following code back to the thread this task was called from. So by doing the above you should avoid some threading delays and hopefully this will resolve the jumpiness.
Though you may need to do a test because with the above, the OnScrolled event will continue to be fired while that work is being completed. So you may want to flag to only run the code to get the new items once, e.g.:
bool _isGettingNewItems;
private async void OnScrolled(object sender, ScrolledEventArgs e)
{
// Don't run the code to get new items if it is already running
if (_isGettingNewItems)
return;
_isGettingNewItems = true;
await Task.Run(async () =>
{
...
});
//then, add the tiles to UI
AddRow();
// finished getting new items, so set the boolean back to false
_isGettingNewItems = false;
}

C# - receiving a file through network stream is blocking WPF UI

I have this method for receiving a file.
public Task Download(IProgress<int> downloadProgress)
{
return Task.Run
(
async () =>
{
var counter = 0;
var buffer = new byte[1024];
while (true)
{
var byteCount = await _networkStream.ReadAsync(buffer, 0, buffer.Length);
counter += byteCount;
downloadProgress.Report(counter);
if (byteCount != buffer.Length)
break;
}
}
);
}
Then in the UI I call it like this:
await Download(progress);
where progress is simply updating a label.
When I run, the UI will be blocked (but after some time it will correctly update the label). I don't understand why, shouldn't Task.Run() create a new thread?
How do I fix this please?
You are calling downloadProgress.Report on an infinite loop without any pause in execution. My educated guess is this means every time execution time is available on the UI thread, the non-UI thread is requesting an operation which will require the UI thread's time (as demanded by the synchronisation context) and therefore clogging it up with invocations.
Essentially, rather than blocking the UI thread with one long execution, you may be blocking it with an un-ending stream of tiny ones.
Try putting a Thread.Sleep(10) in your 'spinlock' while(true) { ... } loop and see if that alleviates the issue.

Why does OpenReadAsync inside a foreach loop hangs the UI thread?

I'm developing an app for UWP.
I need to load a folder that contains around 700 small pictures. This is the method I use to load the pictures into memory:
private async Task<ObservableCollection<ImageSource>> LoadPicturesAsync()
{
var pictureList = new ObservableCollection<ImageSource> { };
pictureFiles.ForEach(async file =>
{
var img = new BitmapImage();
pictureList.Add(img);
var stream = await file.OpenReadAsync();
await img.SetSourceAsync(stream);
});
return pictureList;
}
When this method gets called by the constructor of my view model, the view seems to be blocked (unresponsive) for about 6 seconds.
This is strange because all IO operations are done asynchronously, and the only thing running in UI thread is creating of BitmapImage objects in a foreach loop. This is so fast it shouldn't block the UI thread.
My question is: Why did the UI thread block for 6 seconds knowing that I run all IO operations asynchronously? And how to fix this so UI thread is not blocked?
This is how I call that method:
private async Task Init()
{
PictureList = await LoadPicturesAsync();
}
//constructor
public MainVewModel(){
Init();
}
Why is this happening?
Because you are trying to run so many threads concurrently since you are using the ForEach method of the list and passing an async action into it. Replace the ForEach with a for-each and you'll be fine.
private async Task<ObservableCollection<ImageSource>> LoadPicturesAsync() {
foreach (var file in pictureFiles) {
var stream = await file.OpenReadAsync();
var image = new BitmapImage();
await image.SetSourceAsync(stream);
pictureList.Add(image );
}
}
private async Task Init() {
PictureList = await LoadPicturesAsync();
}
After all, I see you call the Init method in your view-model:
//constructor
public MainVewModel(){
Init();
}
Since Init returns a Task, you need be aware that the PictureList property/field may not be set when construction finishes, so if you attempt to access it immediately after instantiate, you may face a NullReferenceException.
MainViewModel viewModel = new MainViewModel();
var pics = viewModel.PictureList;
var count = pics.Count; // High chance of NullReferenceException
In order to avoid that, you may consider defining an static CreateAsync method for you view-model. More information can be found here.

C# async await for pooling

I need to do some WebRequest to a certain endpoint every 2 seconds. I tried to do it with a Timer, the problem is that every call to the callback function is creating a different Thread and I'm havind some concurrence problems. So I decided to change my implementation and I was thinking about using a background worker with a sleep of two seconds inside or using async await but I don't see the advantages of using async await. Any advice? thank you.
This is the code that I will reimplement.
private void InitTimer()
{
TimerCallback callback = TimerCallbackFunction;
m_timer = new Timer(callback, null, 0, m_interval);
}
private void TimerCallbackFunction(Object info)
{
Thread.CurrentThread.Name = "Requester thread ";
m_object = GetMyObject();
}
public MyObject GetMyObject()
{
MyObject myobject = new MyObject();
try
{
MemoryStream responseInMemory = CreateWebRequest(m_host, ENDPOINT);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyObject));
myObject = (MyObject) xmlSerializer.Deserialize(responseInMemory);
}
catch (InvalidOperationException ex)
{
m_logger.WriteError("Error getting MyObject: ", ex);
throw new XmlException();
}
return myObject;
}
private MemoryStream CreateWebRequest(string host, string endpoint)
{
WebRequest request = WebRequest.Create(host + endpoint);
using (var response = request.GetResponse())
{
return (MemoryStream) response.GetResponseStream();
}
}
EDIT: I have read this SO thread Async/await vs BackgroundWorker
async await is also concurrence. If you have concurrence problems and you want your application to have only one thread, you should avoid using async await.
However the best way to do WebRequest is to use async await, which does not block the main UI thread.
Use the bellow method, it will not block anything and it is recommended by Microsoft. https://msdn.microsoft.com/en-us/library/86wf6409(v=vs.110).aspx
private async Task<MemoryStream> CreateWebRequest(string host, string endpoint)
{
WebRequest request = WebRequest.Create(host + endpoint);
using (var response = await request.GetResponseAsync())
{
return (MemoryStream)response.GetResponseStream();
}
}
You don't mention what the concurrency problems are. It may be that the request takes so long that the next one starts before the previous one finishes. It could also be that the callback replaces the value in my_Object while readers are accessing it.
You can easily make a request every X seconds, asynchronously and without blocking, by using Task.Delay, eg:
ConcurrentQueue<MyObject> m_Responses=new ConcurrentQueue<MyObject>();
public async Task MyPollMethod(int interval)
{
while(...)
{
var result=await SomeAsyncCall();
m_Responses.Enqueue(result);
await Task.Delay(interval);
}
}
This will result in a polling call X seconds after the last one finishes.
It also avoids concurrency issues by storing the result in a concurrent queue instead of replacing the old value, perhaps while someone else was reading int.
Consumers of MyObject would call Dequeue to retrieve MyObject instances in the order they were received.
You could use the ConcurrentQueue to fix the current code too:
private void TimerCallbackFunction(Object info)
{
Thread.CurrentThread.Name = "Requester thread ";
var result=GetMyObject();
m_Responses.Enqueue(result);
}
or
private async void TimerCallbackFunction(Object info)
{
Thread.CurrentThread.Name = "Requester thread ";
var result=await GetMyObjectAsync();
m_Responses.Enqueue(result);
}
if you want to change your GetObject method to work asynchronously.
Since your request seems to take a long time, it's a good idea to make it asynchronous and avoid blocking the timer's ThreadPool thread while waiting for a network response.

Reporting/logging from with in a Task

I have an async call that throws an exception if the given record fails processing. That exception is caught as aggregated exception. There is now, a requirement that I have to log a warning message from my async method. I cannot log to TextBox/Grid as it is not allowed to access controls over different threads and I cannot throw an exception as I actually want to log and continue that task. Here is the parent code that launches a task:
private List<OrganizationUser> GenerateDataList(string importFilePath, int lastUserId = -1)
{
var resultList = new List<OrganizationUser>();
// Note: ReadLine is faster than ReadAllLines
var lineCount = File.ReadLines(importFilePath).Count();
LogMessage(new LogEntry(String.Format(" - File has {0} lines.", lineCount)));
var consistancyCounter = 0;
ResetProgress();
using (var fs = File.Open(importFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var bs = new BufferedStream(fs))
{
using (var sr = new StreamReader(bs))
{
string readLine;
while ((readLine = sr.ReadLine()) != null)
{
if (string.IsNullOrEmpty(readLine) || readLine == "---EOF---")
{
break;
}
try
{
var processLineTask = Task.Run(() => GenerateDataListInternal(nextId++, localReadLine, localConsistancyCounter));
processLineTask.Wait();
var result = processLineTask.Result;
if (result != null)
{
resultList.Add(result);
}
}
catch (AggregateException exp)
{
if (exp.InnerExceptions.Count == 1 && exp.InnerExceptions.Any(x => x is DataFileBadColumnNumbers || x is DataFileGenerateListException))
{
LogMessage(new LogEntry(exp.InnerExceptions[0].Message, LogEntryType.Warning));
}
else if (exp.InnerExceptions.Count == 1 && exp.InnerExceptions.Any(x => x is IndexOutOfRangeException))
{
LogMessage(new LogEntry(String.Format(" - Data cannot be parsed at line #{0}. Data is: {1}", localConsistancyCounter + 1, localReadLine), LogEntryType.Warning));
}
else
{
throw;
}
}
}
if (ProgressBarImport.Value <= ProgressBarImport.Maximum)
{
ProgressBarImport.PerformStep();
}
}
}
}
}
In the above code, GenerateDataListInternal is the method that throws exceptions and now needs to log.
How can I log from within the GenerateDataListInternal method? I have tried the delegate approach and that simply hangs the application. I have a method that logs to Console, Grid and text file (in that order). Any call to that method from asynced methods fail due to Cross thread operation.
This is already provided through the System.IProgress interface and the Progress class, where T can be any class, allowing you to report much more than a simple progress percentage.
IProgress<T>.Report allows you to report progress (or anything else) from inside an asynchronous operation without worrying who will actually handle the report.
Progress<T> will call an action and/or raise an event on the thread where it was created (eg. the UI thread) whenever your task calls .Report
This example from the .NET Framework Blog displays how easy it is to use IProgress<T>:
async Task<int> UploadPicturesAsync(List<Image> imageList, IProgress<int> progress)
{
int totalCount = imageList.Count;
int processCount = await Task.Run<int>(() =>
{
int tempCount = 0;
foreach (var image in imageList)
{
//await the processing and uploading logic here
int processed = await UploadAndProcessAsync(image);
if (progress != null)
{
progress.Report((tempCount * 100 / totalCount));
}
tempCount++;
}
return tempCount;
});
return processCount;
}
The async process starts with this code:
private async void Start_Button_Click(object sender, RoutedEventArgs e)
{
//construct Progress<T>, passing ReportProgress as the Action<T>
var progressIndicator = new Progress<int>(ReportProgress);
//call async method
int uploads=await UploadPicturesAsync(GenerateTestImages(), progressIndicator);
}
Note that progressIndicator is created in the UI thread, so the ReportProgress method will be called on the UI thread.
UPDATE
From the comments it sounds like you are trying to create your own logging solution and have issues with accessing the log file from multiple threads.
The best solution here would be to use a logging library like log4net, NLog or even .NET's Diagnostic classes. All of them work without issues with multiple threads.
IProgress<T> can still help here, as the delegate that handles a Report event can simply write the message to a log file you already created on the UI thread.
You could write something like this:
var progress = new Progress<LogEntry>(entry =>
{
_logFile.WriteLine("{0} : {1}",entry.EntryType,entry.Message);
});
and report from inside a Task with:
var entry=new LogEntry(...);
progress.Report(entry);

Categories

Resources