I'm developing a function to return a collection, generated from an xml file.
Initially, I was using a local xml file for testing, but now I'm ready to have the app download the real xml file from a server. I'm struggling to see how I could do this due to the fact a WebClient object needs to be given an OpenReadCompleted event handler - I cannot return the collection data from this, and also by the time this handler executes, the original function has ended.
My original code is as follows:
public static ObservableCollection<OutletViewModel> GetNear(GeoCoordinate location)
{
ObservableCollection<OutletViewModel> Items = new ObservableCollection<OutletViewModel>();
// Load a local XML doc to simulate server response for location
XDocument xDoc = XDocument.Load("SampleRemoteServer/outlet_list.xml");
foreach (XElement outlet in xDoc.Descendants("outlet"))
{
Items.Add(new OutletViewModel()
{
Name = outlet.Attribute("name").Value,
Cuisine = outlet.Attribute("cuisine").Value
});
}
return Items;
}
How can I load the file in this function, have the event handler run, and then continue the function?
The only was I can think of is to add a loop to keep checking a variable, which is updated by the event handler code... and that doesn't sound like a good solution.
Thanks,
Josh
You move the foreach() loop to the completed event.
And that indeed means you cannot return anything from the original method. Make it a void.
This is how async I/O works, better get used to it. You will need to rethink your design.
You should start to take a look at async programming.
One (old school) way would be to implement a public event and subscribe to that event in the calling class.
However, using callbacks is more elegant. I whipped up a simple (useless, but still conceptually valid) example that you can build upon:
public static void Main(string[] args)
{
List<string> list = new List<string>();
GetData(data =>
{
foreach (var item in data)
{
list.Add(item);
Console.WriteLine(item);
}
Console.WriteLine("Done");
});
Console.ReadLine();
}
public static void GetData(Action<IEnumerable<string>> callback)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += (s, e) =>
{
List<string> data = new List<string>();
for (int i = 0; i < 5; i++)
{
data.Add(e.Result);
}
callback(e.Error == null ? data : Enumerable.Empty<string>());
};
webClient.DownloadStringAsync(new Uri("http://www.google.com"));
}
If you want to jump onto the C# async bandwagon (link for WP7 implementation), you can implement it using the new async and await keywords:
public static async void DoSomeThing()
{
List<string> list = new List<string>();
list = await GetDataAsync();
foreach (var item in list)
{
Console.WriteLine(item);
}
}
public static async Task<List<string>> GetDataAsync()
{
WebClient webClient = new WebClient();
string result = await webClient.DownloadStringTaskAsync(new Uri("http://www.google.com"));
List<string> data = new List<string>();
for (int i = 0; i < 5; i++)
{
data.Add(result);
}
return data;
}
Related
I have several pages in my Xamarin.Forms app that require similar functionality, so I decided to make an external utility class that I can reference in all of them to reduce redundant code.
The code for one of the pages is:
public partial class Incontrol_page : TabbedPage
{
public Incontrol_page()
{
Boolean initialrun = true;
Alert_Generator generator = new Alert_Generator();
InitializeComponent();
Task.Run(() =>
{
while (true)
{
//code not relevant to the question removed here
Device.BeginInvokeOnMainThread(() =>
{
List<Frame> offline_alerts = generator.GenAlerts("incontrol_offline", active_offline_events);
List<Frame> bandwidth_alerts = generator.GenAlerts("incontrol_bandwidth", active_bandwidth_events);
offline_stack.Children.Clear();
bandwidth_stack.Children.Clear();
foreach(Frame frame in offline_alerts)
{
offline_stack.Children.Add(frame);
}
foreach(Frame frame in bandwidth_alerts)
{
bandwidth_stack.Children.Add(frame);
}
});
Thread.Sleep(10000);
}
});
}
}
Essentially the method pulls data from an API and creates a list of frames that differ slightly depending on the kind of data pulled. This part works fine, however I run into functionality problems in the frames themselves. Here is a sample of the code used to make the frames:
public List<Frame> GenAlerts(string type, List<String> active_events)
{
using (WebClient wc = new WebClient())
{
//irrelevant code removed
foreach (var obj in list)
{
//irrelevant code removed
Frame alert_frame = new Frame
{
//irrelevant code removed
Content = new StackLayout
{
//irrelevant code removed
Children =
{
Orientation=Xamarin.Forms.StackOrientation.Horizontal,
Children =
{
GetChildren(obj,color,wc,type)[0],
GetChildren(obj,color,wc,type)[1]
}
}
}
};
return_list.Add(alert_frame);
}
return return_list;
}
}
public List<View> GetChildren(Alert obj, Color color, WebClient wc, string type)
{
//creates info button with popup for the event readout
Button info = new Button()
{
//attributes here
};
info.Clicked += async (sender, args) => await DisplayAlert("Event Info", obj.Change, "Okay");
//creates delete button that deletes the parent alert object from the stack layout and sends the command to the api to remove it from the SQL table
Button delete = new Button()
{
//attributes here
};
delete.Clicked += async (sender, args) => await Remove(obj, wc, type);
List<View> alert_frames = new List<View>();
alert_frames.Add(info);
alert_frames.Add(delete);
return alert_frames;
}
async Task Remove(Alert obj, WebClient wc, string type)
{
View frame = new Frame();
foreach (Frame element in alert_stack.Children)
{
if (element.StyleId == obj.EventKey)
{
frame = element;
}
}
alert_stack.Children.Remove(frame);
//sends the remove command for the event's id to the api
var jsonstr = wc.DownloadString("http://172.30.211.33:5000/remove?db="+type+"&id=" + obj.EventKey);
}
The big problems for me come in the Remove(Alert obj, WebClient wc, string type) task. In a previous implementation when all this was directly in the code behind for each page instead of in a reusable method I was able to simply get the reference for the parent frame and remove the desired element at the press of a button but it isn't that easy from an external method not directly attached to the page. Is it possible for me to pass the reference to alert_stack as an argument of GenAlerts()?
In the same vein, I am not sure how I can make the DisplayAlert() function call actually work from the external method. Is there a way I can get some reference to the parent page calling the function to allow this to actually display an alert on screen?
is it possible for me to pass the reference to alert_stack as an
argument of GenAlerts()?
Yes. Have you tried? It should be as simple as
List<Frame> offline_alerts = generator.GenAlerts(offline_stack, "incontrol_offline", active_offline_events);
and
public List<Frame> GenAlerts(StackLayout parent, string type, List<String> active_events)
Is there a way I can get some reference to the parent page
Yes. You could either pass in an explicit reference to the current page, or do this
App.Current.MainPage.DisplayAlert(...);
I need some help. If you input an Directory into my code, it goes in every folder in that Directory and gets every single file. This way, i managed to bypass the "AccessDeniedException" by using a code, BUT if the Directory is one, which contains alot of Data and folders (example: C:/) it just takes way to much time.
I dont really know how to multithread and i could not find any help on the internet. Is there a way to make the code run faster by multithreading? Or is it possible to ask the code to use more memory or Cores ? I really dont know and could use advise
My code to go in every File in every Subdirectory:
public static List<string> Files = new List<string>();
public static List<string> Exceptions = new List<string>();
public MainWindow()
{
InitializeComponent();
}
private static void GetFilesRecursively(string Directory)
{
try
{
foreach (string A in Directory.GetDirectories(Directory))
GetFilesRecursively(A);
foreach (string B in Directory.GetFiles(Directory))
AddtoList(B);
} catch (System.Exception ex) { Exceptions.Add(ex.ToString()); }
}
private static void AddtoList(string Result)
{
Files.Add(Result);
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
GetFilesRecursively(Textbox1.Text);
foreach(string C in Files)
Textbox2.Text += $"{C} \n";
}
You don't need recursion to avoid inaccessible files. You can use the EnumerateFiles overload that accepts an EnumerationOptions parameter and set EnumerationOptions.IgnoreInaccessible to true:
var options=new EnumerationOptions
{
IgnoreInaccessible=true,
RecurseSubdirectories=true
};
var files=Directory.EnumerateFiles(somePath,"*",options);
The loop that appends file paths is very expensive too. Not only does it create a new temporary string on each iteration, it also forces a UI redraw. You could improve speed and memory usage (which, due to garbage collection is also affecting performance) by creating a single string, eg with String.Join or a StringBuilder :
var text=String.Join("\n",files);
Textbox2.Text=text;
String.Join uses a StringBuilder internally whose internal buffer gets reallocated each time it's full. The previous buffer has to be garbage-collected. Once could avoid even this by using a StringBuilder with a specific capacity. Even a rough estimate can reduce reallocations significantly:
var builder=new StringBuilder(4096);
foreach(var file in files)
{
builder.AppendLine(file);
}
create a class so you can add a private field to count the deep of the directroy.
add a TaskSource<T> property to the class, and await the Task that generated only if the deep out of the limit, and trigger an event so your UI can hook into the action and ask user.
if user cancel , then the task fail, if user confirm, then continue.
some logic code
public class FileLocator
{
public FileLocator(int maxDeep = 6){
_maxDeep = maxDeep;
this.TaskSource = new TaskSource();
this.ConfirmTask = this.TaskSource.Task;
}
private int _maxDeep;
private int _deep;
public event Action<FileLocator> OnReachMaxDeep;
public Task ConfirmTask ;
public TaskSource TaskSource {get;}
public Task<List<string>> GetFilesRecursivelyAsync()
{
var result = new List<string>();
foreach(xxxxxxx)
{
xxxxxxxxxxxxxx;
this._deep +=1;
if(_deep == _maxDeep)
{ OnRichMaxDeep?.Invoke(this); }
if(_deep >= _maxDeep)
{
try{
await ConfirmTask;
continue;
}
catch{
return result;
}
}
}
}
}
and call
var locator = new FileLocator();
locator.OnReachMaxDeep += (x)=> { var result = UI.Confirm(); if(result){ x.TaskSource.SetResult(); else{ x.TaskSource.SetException(new Exception()) } } }
var result = await locator.GetFilesRecursivelyAsync("C:");
I am facing quite a struggle, I want to iterate over a list using Parallel.Foreach.
So picture this
static List<string> proxyList = new List<string>();
static List<string> websiteList = new List<string>();
Each list is looking something like this
192.168.0.1
192.168.0.2
192.168.0.3
192.168.0.4
And the website list
https://google.com
https://spotify.com
https://duckduckgo.com
https://amazon.com
I want to achieve something like this but I have no idea how, no matter how I twist and turn I cant seem to find any logic.
Foreach(var proxy in proxyList)
{
If(proxyIsAlive)
//Try to connect to the first website in the website list
else
//Try the next proxy until I get a working one
//and then try to connect to the most recent one
}
}
The issue I am facing is that I have to idea how to access the websites in the website list, I want to connect to
EDIT: this is what my logic looks like so far
private static void Connect()
{
string tproxy = "";
int port;
foreach (var website in websiteList)
{
foreach (var proxy in proxyList)
{
var proxySplit = proxy.Split(':');
tproxy = proxySplit[0];
port = Convert.ToInt32(proxySplit[1]);
//if(ProxyIsAlive)
}
//Use that proxy down here to connect
}
}
I only want to move out of the proxy foreach IF ProxyIsAlive returns true
Notes :
This uses nested Parallel as per your original question
It assumes you want to check the last good proxy first
If a good proxy is found it updates the success time
It processes each website and proxy in parallel
Note : Parallel.ForEach is suited for CPU-bound tasks, you need to be
careful you aren't just wasting resources blocking threads waiting for
IO operations to complete
Class to hold proxy info
public class Proxy
{
public string Host { get; set; }
public int Port { get; set; }
public DateTime LastSuccess { get; set; }
public Proxy(string value)
{
var proxySplit = value.Split(':');
Host = proxySplit[0];
Port = Convert.ToInt32(proxySplit[1]);
LastSuccess = DateTime.MinValue;
}
}
Code to run in parallel
var proxies = proxyList.Select(x => new Proxy(x)).ToList();
Parallel.ForEach(webSites, new ParallelOptions { MaxDegreeOfParallelism = 4 }, site =>
{
Parallel.ForEach(proxies.OrderByDescending(x => x.LastSuccess), new ParallelOptions { MaxDegreeOfParallelism = 4 }, proxy =>
{
if(!CheckProxy(proxy))
{
//check next proxy
return;
}
// if we found a good proxy
// update the lastSuccess so we check that first
proxy.LastSuccess = DateTime.Now;
// do something to the website
});
});
}
Note : This may not be the best approach, if you have CPU-bound code,
parallelism is appropriate; if you have I/O-bound code, asynchrony is
appropriate. In this case, an HttpClientExtension.GetHttpResponse is
clearly I/O, so the ideal consuming code would be asynchronous.
I would consider looking up the topics Parallel execution for IO bound operations
Existing SO questions deal with this Such as
Parallel execution for IO bound operations
Parallel.ForEach vs Async Forloop in Heavy I/O Ops
So this is what I ended up doing.
private static void ConnectToWebsite()
{
var proxyIP = "";
int port;
foreach (var website in WebsiteList)
{
foreach (var proxy in proxyList)
{
var proxySplit = proxy.Split(':');
proxyIP = proxySplit[0];
var convert = Int32.TryParse(proxySplit[1], out port);
if(HttpClientExtension.GetHttpResponse(getCMYIP, proxyIP, port))
Console.WriteLine(website + proxy);
}
}
}
That will check proxies until it finds a working one.
Now I need to make this async to speed things up.
I am trying to load many pages using the AngleSharp. The idea is that it loads a page, and if this page has a link to the next, loads the next page and so forth, the methods are described like bellow. But I am getting the inner exception:
Specified argument was out of the range of valid values.
Parameter name: index"
I believe is something related with Thread and syncrhronization.
public static bool ContainsNextPage(IDocument document)
{
String href = document.QuerySelectorAll(".prevnext a")[0].GetAttribute("href");
if (href == String.Empty)
return false;
else
return true;
}
public static string GetNextPageUrl(IDocument document)
{
return document.QuerySelectorAll(".prevnext a")[0].GetAttribute("href");
}
public static async Task<IDocument> ParseUrlSynch(string Url)
{
var config = new Configuration().WithDefaultLoader();
IDocument document = await BrowsingContext.New(config).OpenAsync(Url);
return document;
}
public static async Task<ConcurrentBag<IDocument>> GetAllPagesDOMs(IDocument initialDocument)
{
ConcurrentBag< IDocument> AllPagesDOM = new ConcurrentBag< IDocument>();
IDocument nextPageDOM;
IDocument currentDocument = initialDocument;
if (initialDocument != null)
{
AllPagesDOM.Add(initialDocument);
}
while (ContainsNextPage(currentDocument))
{
String nextPageUrl = GetNextPageUrl(currentDocument);
nextPageDOM = ParseUrlSynch(nextPageUrl).Result;
if (nextPageDOM != null)
AllPagesDOM.Add(nextPageDOM);
currentDocument = nextPageDOM;
}
return AllPagesDOM;
}
static void Main(string[] args)
{
List<IDocument> allPageDOMs = new List<IDocument>();
IDocument initialDocument = ParseUrlSynch(InitialUrl).Result;
List<String> urls = new List<string>();
List<Subject> subjects = new List<Subject>();
IHtmlCollection<IElement> subjectAnchors = initialDocument.QuerySelectorAll(".course_title a");
String[] TitleAndCode;
String Title;
String Code;
String Description;
IDocument currentDocument = initialDocument;
ConcurrentBag<IDocument> documents =
GetAllPagesDOMs(initialDocument).Result; //Exception in here
...
}
Error message is caused by this code:
document.QuerySelectorAll(".prevnext a")[0]
One of your documents doesn't have any anchors inside prevnext. Maybe it's first page, maybe the last, either way you need to check the array for it's length.
Also blocking call on async method is a bad practice and should be avoided. You'll get the deadlock in any UI app. The only reason you don't get it now is that you're in console app.
Your instincts are correct, if you are using this from an application with a non-default SynchronizationContext such as WPF, Win Forms, or ASP.NET then you will have a deadlock because you are synchronously blocking on an async Task returning function (this is bad and should be avoided). When the first await is reaching inside of the blocking call, it will try to post the continuation to the current SyncronizationContext, which will be already locked by the blocking call (if you use .ConfigureAwait(false) you avoid this, but that is a hack in this case).
A quick fix would be to use async all the way through by changing:
nextPageDOM = ParseUrlSynch(nextPageUrl).Result;
with:
nextPageDOM = await ParseUrlSynch(nextPageUrl);
After you get stung by this a few times, you'll learn to have alarm bells go off in your head every time you block an asynchronous method.
I will try to tell my problem in as simple words as possible.
In my UWP app, I am loading the data async wise on my Mainpage.xaml.cs`
public MainPage()
{
this.InitializeComponent();
LoadVideoLibrary();
}
private async void LoadVideoLibrary()
{
FoldersData = new List<FolderData>();
var folders = (await Windows.Storage.StorageLibrary.GetLibraryAsync
(Windows.Storage.KnownLibraryId.Videos)).Folders;
foreach (var folder in folders)
{
var files = (await folder.GetFilesAsync(Windows.Storage.Search.CommonFileQuery.OrderByDate)).ToList();
FoldersData.Add(new FolderData { files = files, foldername = folder.DisplayName, folderid = folder.FolderRelativeId });
}
}
so this is the code where I am loading up a List of FolderData objects.
There in my other page Library.xaml.cs I am using that data to load up my gridview with binding data.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
try
{
LoadLibraryMenuGrid();
}
catch { }
}
private async void LoadLibraryMenuGrid()
{
MenuGridItems = new ObservableCollection<MenuItemModel>();
var data = MainPage.FoldersData;
foreach (var folder in data)
{
var image = new BitmapImage();
if (folder.files.Count == 0)
{
image.UriSource = new Uri("ms-appx:///Assets/StoreLogo.png");
}
else
{
for (int i = 0; i < folder.files.Count; i++)
{
var thumb = (await folder.files[i].GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode.VideosView));
if (thumb != null) { await image.SetSourceAsync(thumb); break; }
}
}
MenuGridItems.Add(new MenuItemModel
{
numberofvideos = folder.files.Count.ToString(),
folder = folder.foldername,
folderid = folder.folderid,
image = image
});
}
GridHeader = "Library";
}
the problem I am facing is that when i launch my application, wait for a few seconds and then i navigate to my library page, all data loads up properly.
but when i try to navigate to library page instantly after launching the app, it gives an exception that
"collection was modified so it cannot be iterated"
I used the breakpoint and i came to know that if i give it a few seconds the List Folder Data is already loaded properly asyncornously, but when i dnt give it a few seconds, that async method is on half way of loading the data so it causes exception, how can i handle this async situation? thanks
What you need is a way to wait for data to arrive. How you fit that in with the rest of the application (e.g. MVVM or not) is a different story, and not important right now. Don't overcomplicate things. For example, you only need an ObservableCollection if you expect the data to change while the user it looking at it.
Anyway, you need to wait. So how do you wait for that data to arrive?
Use a static class that can be reached from everywhere. In there put a method to get your data. Make sure it returns a task that you cache for future calls. For example:
internal class Data { /* whatever */ }
internal static class DataLoader
{
private static Task<Data> loaderTask;
public static Task<Data> LoadDataAsync(bool refresh = false)
{
if (refresh || loaderTask == null)
{
loaderTask = LoadDataCoreAsync();
}
return loaderTask;
}
private static async Task<Data> LoadDataCoreAsync()
{
// your actual logic goes here
}
}
With this, you can start the download as soon as you start the application.
await DataLoader.LoadDataAsync();
When you need the data in that other screen, just call that method again. It will not download the data again (unless you set refresh is true), but will simply wait for the work that you started earlier to finish, if it is not finished yet.
I get that you don't have enough experience.There are multiple issues and no solution the way you are loading the data.
What you need is a Service that can give you ObservableCollection of FolderData. I think MVVM might be out of bounds at this instance unless you are willing to spend a few hours on it. Though MVVM will make things lot easier in this instance.
The main issue at hand is this
You are using foreach to iterate the folders and the FolderData list. Foreach cannot continue if the underlying collection changes.
Firstly you need to start using a for loop as opposed to foreach. 2ndly add a state which denotes whether loading has finished or not. Finally use observable data source. In my early days I used to create static properties in App.xaml.cs and I used to use them to share / observe other data.