public Form1()
{
InitializeComponent();
loadComboEmail();
}
private void loadComboEmail()
{
string path = Directory.GetCurrentDirectory();
string build = (path + "\\" + "email.txt");
string[] lines = System.IO.File.ReadAllLines(build);
comboEmail.Items.AddRange(lines);
comboEmail.SelectedIndex=0;
}
I've got a combobox that I wanted to load with the client email addresses which are stored in an text file. Using the loadComboEmail() I read from the text file and load the Combobox. Am I doing something that is considered bad practice by calling the loadComboEmail at the form startup? If so, how do I properly read from the textfile and then load it into the combo, so when the form loads, it has the necessary data?
No, this seems quite legit. You don't have to worry as this is the way all things get loaded in WinForms... This will block the UI thread for a short amount of time, but since your not going to load huge masses of something, you won't even notice it.
When you start performing a greater amount of Actions and big files you should think about using a BackgroundWorker or a Task!
Anyway you should also consider using the following instead of your Code:
private void loadComboEmail()
{
string path = System.IO.Path.GetDirectoryName(Application.ExecutablePath); //seems to be the same to me, but is safer
string build = System.IO.Path.Combine(path, "email.txt"); //much more safer then simple string addition
string[] lines = System.IO.File.ReadAllLines(build);
comboEmail.Items.AddRange(lines);
comboEmail.SelectedIndex = 0;
}
You can use:
Task task = Task.Factory.StartNew(() =>
{
// Background work
loadComboEmail();
}).ContinueWith((t) =>
{
//If you need to execute something after the loading process.
});
In order to update the UI thread, you can maybe do the reading on another thread and when the list of emails is loaded, just update it.
Task.Factory.StartNew(() =>
{
// Background work - read the file
}).ContinueWith((t) => {
// Update UI thread here
}, TaskScheduler.FromCurrentSynchronizationContext());
Using the constructor to load that is not considered as a bad practice. Read the answer of Hans Passant about when you should use the Load Event of the window. What setup code should go in Form Constructors versus Form Load event?
Altough as said in comments, you are blocking the UI Thread. In the constructor the keyword await cannot be used. So you have to 'Fire and forget'. When using the Load event you can use await but event handlers are async void. So they are not awaited, you have stil 'Fire and forget'.
public Form1()
{
InitializeComponent();
loadComboEmail();
}
private async Task loadComboEmail()
{
string path = Directory.GetCurrentDirectory();
string build = (path + "\\" + "email.txt");
string[] lines = await Task.Run(() => File.ReadAllLines(build));
comboEmail.Items.AddRange(lines);
comboEmail.SelectedIndex=0;
}
Related
I am working on a application which will check the ftp server and list all files of directory, that work perfectly without BACKGROUND WORKER but when i use Background worker, lot of problem occurs.
The first problem was that i cannot access ListView from BackGround worker, i used another method (store the list in Array and then Update on BackGround Worker Process Complete) but it did'nt worked.
Then i used another form which was Hidden and doing same function but the program still stuck at listing FTP files.
Actually, i just want to list files of this directory(ftp://blah/subdir/[files are here]) to ListView1. How it is Possible without Freezing UI? i tried to use Background worker but it did'nt worked, how can use it to work?
Here is Code
function
public string[] ListDirectory()
{
string hostdir = textBoxX2.Text + textBoxX3.Text;
var request = createRequest(hostdir,WebRequestMethods.Ftp.ListDirectory);
using (var response = (FtpWebResponse)request.GetResponse())
{
using (var stream = response.GetResponseStream())
{
using (var reader = new StreamReader(stream, true))
{
while (!reader.EndOfStream)
{
listView1.Items.Add(reader.ReadLine());
}
}
}
}
List<string> l = new List<string>();
return l.ToArray();
}
private FtpWebRequest createRequest(string uri, string method)
{
var r = (FtpWebRequest)WebRequest.Create(uri);
r.Credentials = new NetworkCredential(textBoxX4.Text, textBoxX5.Text);
r.Method = method;
return r;
}
The Code above Works But freezes UI, and when i use background worker, some of functions not working like, cannot access ListView1 from Object it was created on or Something Similar.
Any Help Appreciated...
Thanks
You could try this approach - the Invoke runs the action on the appropriate thread:
string line = reader.ReadLine();
listView1.Invoke((MethodInvoker)delegate {
listView1.Items.Add(line); // runs on UI thread
});
In your case, you know you are not on the UI thread, but if you have a function that could be called either from the UI thread or a worker thread, you need to check InvokeRequired:
string line = reader.ReadLine();
if ( listView1.InvokeRequired ) {
listView1.Invoke((MethodInvoker)delegate {
listView1.Items.Add(line); // runs on UI thread
});
}
else {
// we're already on UI thread - work on ListView1 directly
listView1.Items.Add(line);
}
I am trying develop a Windows App and run into issues.
I have a MainPage.xaml and 2 others StartScreen.xaml and Player.xaml.
I am switching content of the MainPage if certain conditions are true.
So I have an event in StartScreen it checks if a directory exist or not but it throws me every time an error.
private void GoToPlayer_Click(object sender, RoutedEventArgs e)
{
if (Directory.Exists(this.main.workingDir + "/" + IDText.Text + "/Tracks")) // Error occurs here
{
this.main.Content = this.main.player; //here i switch between different ui forms
}
else
{
MessageBox.Text = "CD not found";
IDText.Text = "";
}
}
When it hit the else branch everything is fine but when the dir is available I get the following error message:
An exception of type 'System.InvalidOperationException' occurred in System.IO.FileSystem.dll but was not handled in user code
Additional information: Synchronous operations should not be performed on the UI thread. Consider wrapping this method in Task.Run.
Even if I commenting the code in the if branch out the error still comes.
I tried this:
private async void GoToPlayer_Click(object sender, RoutedEventArgs e)
{
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
if (Directory.Exists(this.main.workingDir + "/" + IDText.Text + "/Tracks")) // Error occurs here
{
this.main.Content = this.main.player; //here i switch between different ui forms
}
else
{
MessageBox.Text = "CD not found";
IDText.Text = "";
}
});
}
Still the same error, as I understood this should be run asynchronous and wait until the code complete, but it doesn't seems so. I also tried bunch other stuff but still get the errors.
I don't know how to fix that, could someone please explain why this is happening and how to fix that.
As the exception says, you are not allowed to call Directory.Exists synchronously in the UI thread. Putting the whole code block in a Dispatcher action still calls it in the UI thread.
In a UWP app you would usually use the StorageFolder.TryGetItemAsync method to check if a file or folder exists:
private async void GoToPlayer_Click(object sender, RoutedEventArgs e)
{
var folder = await StorageFolder.GetFolderFromPathAsync(main.workingDir);
if ((folder = await folder.TryGetItemAsync(IDText.Text) as StorageFolder) != null &&
(folder = await folder.TryGetItemAsync("Tracks") as StorageFolder) != null)
{
...
}
}
Note that you may still get an UnauthorizedAccessException when the application is not allowed to access main.workingDir.
[Update July 22 2018 with example]
The error message tells you everything you need to know:
Consider wrapping this method in Task.Run
You should wrap the code in a call to Task.Run. This will ensure it runs on a background thread.
Here is a simple example:
var picker = new FolderPicker();
picker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
picker.FileTypeFilter.Add(".mp3");
var folder = await picker.PickSingleFolderAsync();
var result = await Task.Run(() => Directory.Exists(Path.Combine(folder.Path, "foobar")));
if (result)
{
Debug.WriteLine("Yes");
}
else
{
Debug.WriteLine("No");
}
Background information in case anyone cares:
I assume, based on your sample code, that you are reading the Music Library. In this case, the .NET file APIs go through the WinRT APIs under the covers since it is a brokered location. Since the underlying WinRT APIs are async, .NET has to do work to give you the illusion of synchronous behaviour, and they don't want to do that on the UI thread.
If you were only dealing with your own local data folders, the .NET APIs would use the underlying Win32 APIs (which are synchronous already) and so would work without any background thread requirements.
Note that on my machine, this appears to work even on the UI thread, so it might depend on the version of Windows that you are targeting.
I have a BackgroundTask which generates some text and then saves it as a file to a LocalFolder. I need to get this file with my main project (same VS solution) after it's been generated by the BackgroundTask and do some next work with it. The background task is triggered both manually by the user (via a "Reload" button) and every 15 mins by a TimeTrigger.
This is the relevant code snippet:
syncTrigger.RequestAsync();
articles = await getCachedArticles("");
How can I tell the getCachedArticles method to wait until after previous request finishes before running? Thank you very much!
If I understand it correctly, what you need to do is to wait for the BackgroundTaskRegistration.Completed event to fire.
One approach would be to create an extension method that returns a Task that completes when the event fires:
public static Task<BackgroundTaskCompletedEventArgs> CompletedAsync(
this BackgroundTaskRegistration registration)
{
var tcs = new TaskCompletionSource<BackgroundTaskCompletedEventArgs>();
BackgroundTaskCompletedEventHandler handler = (s, e) =>
{
tcs.SetResult(e);
registration.Completed -= handler;
};
registration.Completed += handler;
return tcs.Task;
}
You would then use it like this:
var taskCompleted = registration.CompletedAsync();
await syncTrigger.RequestAsync();
await taskCompleted;
articles = await getCachedArticles("");
Note that the code calls CompletedAsync() before calling RequestAsync() to make sure the even handler is registered before the task is triggered, to avoid the race condition where the task completes before the handler is registered.
Whenever you want your current context to wait for an asynchronous method to complete before continuing, you want to use the await keyword:
await syncTrigger.RequestAsync(); //the line below will not be executed until syncTrigger.RequestAsync() completes its task
articles = await getCachedArticles("");
I would recommend reading through the await C# Reference Article in order to get the full picture of how it works.
EDIT: svick's answer shows the best approach, I even wrote a blog post about it. Below is my original answer with a couple of indirect alternatives that might work for some cases.
As others have noted, awaiting syncTrigger.RequestAsync() won't help, although it is a good idea nevertheless. It will resume execution when the background task was succesfully triggered, and as such allow you to check if it failed for any reason.
You could create an app service to make it work when you are triggering the background task from the application. App services behave similar to web services. They are running in a background task, but have request-response semantics.
In the background service you would need to handle the RequestReceived event:
public void Run(IBackgroundTaskInstance taskInstance)
{
var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
appServiceconnection = details.AppServiceConnection;
appServiceconnection.RequestReceived += OnRequestReceived;
}
private async void OnRequestReceived(AppServiceConnection sender,
AppServiceRequestReceivedEventArgs args)
{
var messageDeferral = args.GetDeferral();
ValueSet arguments = args.Request.Message;
ValueSet result = new ValueSet();
// read values from arguments
// do the processing
// put data for the caller in result
await args.Request.SendResponseAsync(result);
messageDeferral.Complete();
}
In the client you can then call the app service:
var inventoryService = new AppServiceConnection();
inventoryService.AppServiceName = "from manifest";
inventoryService.PackageFamilyName = "from manifest";
var status = await inventoryService.OpenAsync();
var arguments = new ValueSet();
// set the arguments
var response = await inventoryService.SendMessageAsync(arguments);
if (response.Status == AppServiceResponseStatus.Success)
{
var result = response.Message;
// read data from the result
}
Check the linked page for more information about app services.
You could call the same app service from the scheduled background task as well, but there would be no way to get notified when the processing was completed in this case.
Since you've mentioned that you're exchanging data via a file in LocalFolder your application could try monitoring the changes to that file instead:
private async Task Init()
{
var storageFolder = ApplicationData.Current.LocalFolder;
var monitor = storageFolder.CreateFileQuery();
monitor.ContentsChanged += MonitorContentsChanged;
var files = await monitor.GetFilesAsync();
}
private void MonitorContentsChanged(IStorageQueryResultBase sender, object args)
{
// react to the file change - should mean the background task completed
}
As far as I know you can only monitor for all the changes in a folder and can't really determine what changed inside the event handler, so for your case it would be best to have a separate sub folder containing only the file saved by the background task once it completes. This way the event would only get raised when you need it to.
You'll have to check for yourself whether this approach works reliably enough for you, though.
You need to wait the RequestAsync method completed with the result
https://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.background.devicetriggerresult
after that I suggest to wait a few seconds with task.delay and try to get the data.
Update:
When you have the device trigger result you need to check this result before to try to get the data after that you can suppose that you have the data saved. I suggested before to use Task.Delay just to wait a few seconds to be sure all data is saved because sometimes the process take some milliseconds more than is expected. I did this because we don't have an event like TriggerCompleted I needed to create my own approach.
I did this before in my app and it works very well.
Still trying to wrap my head around async/await. I have the following method for drag/drop loading:
private async void p_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
List<string> CurvesToLoad = new List<string>();
List<string> TestsToLoad = new List<string>();
foreach (string file in files)
{
if (file.ToUpper().EndsWith(".CCC"))
CurvesToLoad.Add(file);
else if (file.ToUpper().EndsWith(".TTT"))
TestsToLoad.Add(file);
}
//SNIPPET IN BELOW SECTION
foreach (string CurvePath in CurvesToLoad)
{
Curve c = new Curve(CurvePath);
await Task.Run(() =>
{
c.load();
c.calculate();
});
AddCurveControls(c);
}
//END SNIPPET
foreach (string TestPath in TestsToLoad)
{
Test t = new Test(TestPath);
await Task.Run(() =>
{
t.load();
});
AddTestControls(t);
}
}
It is non-blocking as I expected - I am able to navigate between tabs of the TabControl as multiple items are loaded and I can see each tab pop up as it complete loading.
I then tried to convert to this:
private Task<Curve> LoadAndCalculateCurve(string path)
{
Curve c = new Curve(path);
c.load();
c.calculate();
return Task.FromResult(c);
}
And then replace the marked snippet from the first code block with:
foreach (string CurvePath in CurvesToLoad)
{
Curve c = await LoadAndCalculateCurve(CurvePath);
AddCurveControls(c);
}
And it becomes blocking - I can't navigate through tabs as it's loading, and then all of the loaded items appear at once when they are completed. Just trying to learn and understand the differences at play here - many thanks in advance.
EDIT:
Updated LoadAndCalculateCurve():
private async Task<Curve> LoadAndCalculateCurve(string path)
{
Curve c = new Curve(path);
await Task.Run(() => {
c.load();
c.calculate();
});
return c;
}
Async methods do not execute in a different thread, await does not start a thread. async merely enables the await keyword and await waits for something that already runs.
All of that code is running on the UI thread.
So basically this is what is happening to my knowledge.
In your first code you write
foreach (string CurvePath in CurvesToLoad)
{
Curve c = new Curve(CurvePath);
await Task.Run(() =>
{
c.load();
c.calculate();
});
AddCurveControls(c);
}
this does the async flow as expected because you are using the Task.Run which adds the work to the ThreadPool queue.
In your second try you don't do this and you are using the same task. Try to use the same logic of (Task.Run) in the second try and I think it will work
The implementation of your LoadAndCalculateCurve method is to synchronously create a Curve, synchronously load it and perform the calculation, and then return the result wrapped in a Task. Nothing about this is asynchronously. When you await it it will invoke the method, do all of the synchronous work to get the (already completed) task, and then add a continuation to that (already completed) task that will fire immediately.
When you instead use Task.Run you're scheduling those long running operations to take place in another thread and immediately returning a (not yet completed) Task that you can use to run code when it does eventually finish its work.
I have problems, updating a WPF ListView Bound to a an ObservableCollection<T> within an Task Thread using (Task Parallel Library)
I have an Small Tool reading Edifact Files, and displaying an Count of each Segment (first three letters of a Line).
The contained Segments with their Counts are displayed in an Listbox.
Wenn I initially Load a File all works fine, and I see the GUI Counting up the Segments.
My Programm allowed switching to another File, If I do that (using exactly the same Code) it failes with the following Exception.
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
Here is the Code that fails
public class SegementLineCollection : ObservableCollection<SegmentLine>
{
public void IncrementCount(string Segment)
{
Segment = Segment.ToUpper();
SegmentLine line;
if (this.Select(x => x.SegmentName).Contains(Segment))
{
line = this.Where(x => x.SegmentName == Segment).FirstOrDefault();
}
else
{
line = new SegmentLine();
line.SegmentName = Segment;
this.Add(line); // <--- At this point comes the Exception
}
line.Count++;
}
}
Here is the TPL Call I use:
private string _FileName;
public string FileName
{
get
{
return _FileName;
}
set
{
_FileName = value;
OnPropertyChanged("FileName");
if (!String.IsNullOrEmpty(value))
new Task(() => StartFile()).Start();
}
}
Does anyone have any hit for me?
------------ E D I T ------------------
The provided Answers using TaskScheduler.FromCurrentSynchronizationContext() or Dispatcher did not do the Trick!
Is it possible that changing the Binding when loading the new does the trick.
Here is the Binding I use, the Reader Onject is Switched in the ViewModel, and the GUI is Notfied with INotifyPropertyChanged implementation
Use a dispatcher to access the collection:
if (Dispatcher.CurrentDispatcher.CheckAccess())
this.Add(...)
else
Dispatcher.CurrentDispatcher.Invoke(() => this.Add(...));
You need to call IncrementCount on the GUI thread.
With TPL you can use TaskScheduler.FromCurrentSynchroniztionContext() in your task or continuation.
var task = new Task<string>(() => { return "segment"; })
var task2 = task.ContinueWith(t => IncrementCount(t.Result),
TaskScheduler.FromCurrentSynchroniztionContext());
task.Start();
As you are acting on different thread you need to use Dispatcher.BeginInvoke to run an updates on a collection bound to UI
I have a solution for this kind of problems in the following blog..
http://bathinenivenkatesh.blogspot.co.uk/2011/07/wpf-build-more-responsive-ui.html
it got detailed explanation and code snippets...