c# - Call Interclass Asynch Task - c#

I am kinda new to Xamarin.Android and still have a steep learning curve ;-). But i approached a problem which i don't find a solution even after a few days of googling and YouTube-ing.
Do somebody know how to call a async Task from another Class?
i want to call the public async Task ReadStringAsync() from the class MainActivity. How can i do that?
i want to call this:
class TextfileRead
{
string[] StrData;
String filename = "Arbeitszeiten.txt";
String filepath = "myFileDir";
String fileContent = "";
public async Task<string> ReadStringAsync()
{
var backingFile = Path.Combine(filepath, filename);
if (backingFile == null || !System.IO.File.Exists(backingFile))
{
return "oO, irgendwas ging schief";
}
string line;
using (var reader = new StreamReader(backingFile, true))
{
//string line;
while ((line = await reader.ReadLineAsync()) != null)
{
//return line;
}
}
return line;
}

In MainActiviy.cs, you could use following ways to call public async Task ReadStringAsync().
public async void getValue()
{
TextfileRead textfileRead = new TextfileRead();
string value = await textfileRead.ReadStringAsync();
}

Related

Why Task.Run not processing all lines on Task?

Im using WebApi to Deserialize Object on client side, witch contains some lightweight images, the code reads:
private void Button_Click(object sender, object e)
{
LoadApi();
}
private async void LoadApi()
{
using (var client = new HttpClient())
{
var responseMessage = await client.GetAsync("http://" +
TxtIP.Text + "/api/prod");
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
List<ClsProd> lstData = new List<ClsProd>();
var jsonResponse = await
responseMessage.Content.ReadAsStringAsync();
if (jsonResponse != null)
{
lstData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ClsProd>>(jsonResponse);
}
ListView1.ItemsSource = lstData;
}
}
}
my ClsProd looks witch get all data from Web Api is:
public class ClsProd : System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg { get; set; }
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
ClsImgBase64 CImg = new ClsImgBase64();
CImg.EvtResult += CImg_EvtResult;
CImg.Start(imax);
}
private void CImg_EvtResult(ImageSource e)
{
ImgPg = e;
NotifyPropertyChanged("ImgPg");
}
}
All data is properly fetch and displayed on list, including string SImax witch is image encoded as Base64 string. The only problem is image conversion from base64 string to image is not happening.
Here is my class it does not pass the 1st statment on Task.Run, please help me find what is wrong. Also same funcition works when called from async void.
public class ClsImgBase64
{
public event Action<ImageSource> EvtResult;
public ClsImgBase64()
{
}
public void Start(string s)
{
System.Threading.Tasks.Task.Run(async () =>
{
//read stream
byte[] bytes = Convert.FromBase64String(s);
var image = bytes.AsBuffer().AsStream().AsRandomAccessStream();
//decode image
//var decoder = await BitmapDecoder.CreateAsync(image);
image.Seek(0);
//create bitmap
var output = new WriteableBitmap(1, 1);
await output.SetSourceAsync(image);
if (EvtResult != null)
{
EvtResult(output);
}
});
}
}
As per async void there's probably an Exception thrown which was lost and not displayed bacause the executing code is not awaited. Let's fix it.
Web part
avoid async void in methods that's aren't event handlers, also handle all possible exceptions in async void method
HttpClient is intended to be instantiated once per app rather than per use
HttpResponseMessage is IDisposable
private async void Button_Click(object sender, object e)
{
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
// show ex.Message here in UI or log it
}
}
private static readonly HttpClient _client = new HttpClient();
private async Task LoadDataAsync()
{
using var response = await _client.GetAsync($"http://{TxtIP.Text}/api/prod");
string json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
List<ClsProd> data = JsonConvert.DeserializeObject<List<ClsProd>>(json);
ListView1.ItemsSource = data;
await DecodeAllImagesAsync(data);
}
// decoding all at once asynchronously, see implementation below
private Task DecodeAllImagesAsync(List<ClsProd> data)
{
return Task.WhenAll(data.Select(item => item.DecodeImageAsync()).ToArray());
}
Consider using System.Text.Json to deserealize instead of old Newtonsoft.Json. It would allow to deserealize response.Content as Stream, faster with less memory consumption e.g:
using var stream = await response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync();
List<ClsProd> data = await JsonSerializer.DeserealizeAsync<List<ClsProd>>(stream);
Data part
Use using directives at the beggining of the code to attach namespaces that will help not to repeat namespaces in the code explicitly
using System.ComponentModel;
It makes possible to write INotifyPropertyChanged instead of System.ComponentModel.INotifyPropertyChanged. I'll remove inlined namespaces below.
don't start long-running job from a constructor, it's unpredictable behavior because costructor must be always successful. Start loading images later. Also constructor cannot await asynchronous tasks. Separate method can.
public class ClsProd : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ImageSource _imgPg;
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg
{
get => _imgPg;
set
{
_imgPg = value;
NotifyPropertyChanged();
}
}
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
}
public async Task DecodeImageAsync()
{
ImgPg = await ClsImgBase64.DecodeAsync(SImax);
}
}
Decoder
As now it's awaitable and doesn't need a callback, decoding method doesn't interact with the instance data. So, it can be static.
public static class ClsImgBase64
{
public static async Task<ImageSource> DecodeAsync(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
using var stream = bytes.AsBuffer().AsStream().AsRandomAccessStream();
// stream.Seek(0); // not sure if it needed
var decoder = await BitmapDecoder.CreateAsync(stream);
var pixelData = await decoder.GetPixelDataAsync();
var pixelArray = pixelData.DetachPixelData();
var bitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
await bitmap.PixelBuffer.AsStream().WriteAsync(pixelArray, 0, pixelArray.Length);
return bitmap;
}
}
Decoder's code based on this answer.
If it will be laggy, try to wrap 2 Decoder's lines with Task.Run. Only if it will be laggy.
using var stream = await Task.Run(() =>
{
byte[] bytes = Convert.FromBase64String(base64);
return bytes.AsBuffer().AsStream().AsRandomAccessStream();
});
Finally: give classes, methods and other things more clear names, that would make the code maintainable.

Parse .net SDK Get Object

I have been trying to return the string of the result but it doesn't return anything. When I do Console.WriteLine it shows the link.
But the line:
s = nzk.Get<string>("link");
doesn't do anything, and I don't know why.
Here's my code:
public string getlink(String ID)
{
ParseClient.Initialize(new ParseClient.Configuration
{
ApplicationId = "xxxxxxxxxxxxxx5335c1fxxx0f19efxxxx06787e",
Server = "http://api.assintates.net/parse/"
});
string s = "";
ParseQuery<ParseObject> query = ParseObject.GetQuery("test");
query.GetAsync(ID).ContinueWith(t =>
{
ParseObject nzk = t.Result;
Console.WriteLine(nzk.Get<string>("link")); // this works
s = nzk.Get<string>("link");// this doesn't work
});
return s;
}
class Program
{
static void Main(string[] args)
{
g_get x = new g_get();
Console.WriteLine(x.getlink("iLQLJKPoJA")); // shows nothing since i initialized the s with ""
Console.ReadLine();
}
}
Here is a little example to demonstrate your problem:
static void Main(string[] args)
{
Console.WriteLine(GetString());
Console.ReadLine();
}
private static async Task TimeConsumingTask()
{
await Task.Run(() => System.Threading.Thread.Sleep(100));
}
private static string GetString()
{
string s = "I am empty";
TimeConsumingTask().ContinueWith(t =>
{
s = "GetString was called";
});
return s;
}
Your output will be the following:
I am empty
Why? The thing to deal with is the ContinueWith()-function (see msdn).
ContinueWith returns you the Task-object. You have to await this task and in your code you didn't await it.
So simple solution call wait on your Task-object.
string s = "";
ParseQuery<ParseObject> query = ParseObject.GetQuery("test");
query.GetAsync(ID).ContinueWith(t =>
{
ParseObject nzk = t.Result;
Console.WriteLine(nzk.Get<string>("link")); // this works
s = nzk.Get<string>("link");// this doesn't work
}).Wait();
return s;
Here some more information about asynchronous programming in C#.
Edit: Some more information
You will see the console output because your task will be run anyway. But it will be run after you returned your string.

Extract an archive with progress bar?

How i can use an progress bar in this case?
void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
//System.Windows.MessageBox.Show("Update Complete!", "Message", MessageBoxButton.OK, MessageBoxImage.Information);
Uri uri = new Uri(url);
string filename = System.IO.Path.GetFileName(uri.AbsolutePath);
ZipFile.ExtractToDirectory(filePathDir + "/" + filename, filePathDir);
}
EDIT:
#Alessandro D'Andria , But in this case?:
WebClient wc = new WebClient();
Stream zipReadingStream = wc.OpenRead(url);
ZipArchive zip = new ZipArchive(zipReadingStream);
ZipFileExtensions.ExtractToDirectory(zip, filePathDir);
You can see the source of ExtractToDirectory on GitHub, the only thing you need to do is pass in a Progress<ZipProgress> and call it inside the foreach loop.
//This is a new class that represents a progress object.
public class ZipProgress
{
public ZipProgress(int total, int processed, string currentItem)
{
Total = total;
Processed = processed;
CurrentItem = currentItem;
}
public int Total { get; }
public int Processed { get; }
public string CurrentItem { get; }
}
public static class MyZipFileExtensions
{
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, IProgress<ZipProgress> progress)
{
ExtractToDirectory(source, destinationDirectoryName, progress, overwrite: false);
}
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, IProgress<ZipProgress> progress, bool overwrite)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (destinationDirectoryName == null)
throw new ArgumentNullException(nameof(destinationDirectoryName));
// Rely on Directory.CreateDirectory for validation of destinationDirectoryName.
// Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists:
DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
string destinationDirectoryFullPath = di.FullName;
int count = 0;
foreach (ZipArchiveEntry entry in source.Entries)
{
count++;
string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, entry.FullName));
if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase))
throw new IOException("File is extracting to outside of the folder specified.");
var zipProgress = new ZipProgress(source.Entries.Count, count, entry.FullName);
progress.Report(zipProgress);
if (Path.GetFileName(fileDestinationPath).Length == 0)
{
// If it is a directory:
if (entry.Length != 0)
throw new IOException("Directory entry with data.");
Directory.CreateDirectory(fileDestinationPath);
}
else
{
// If it is a file:
// Create containing directory:
Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath));
entry.ExtractToFile(fileDestinationPath, overwrite: overwrite);
}
}
}
}
This is used like
public class YourClass
{
public Progress<ZipProgress> _progress;
public YourClass()
{
// Create the progress object in the constructor, it will call it's ReportProgress using the sync context it was constructed on.
// If your program is a UI program that means you want to new it up on the UI thread.
_progress = new Progress<ZipProgress>();
_progress.ProgressChanged += Report
}
private void Report(object sender, ZipProgress zipProgress)
{
//Use zipProgress here to update the UI on the progress.
}
//I assume you have a `Task.Run(() => Download(url, filePathDir);` calling this so it is on a background thread.
public void Download(string url, string filePathDir)
{
WebClient wc = new WebClient();
Stream zipReadingStream = wc.OpenRead(url);
ZipArchive zip = new ZipArchive(zipReadingStream);
zip.ExtractToDirectory(filePathDir, _progress);
}
//...
Maybe something like this can work for you:
using (var archive = new ZipArchive(zipReadingStream))
{
var totalProgress = archive.Entries.Count;
foreach (var entry in archive.Entries)
{
entry.ExtractToFile(destinationFileName); // specify the output path of thi entry
// update progess there
}
}
It's simple a workaround to keep track of the progress.

Async Functions making code not working

First, apologize me for the title... I haven't found any suiting my single case :P
I need to download an INI file to fill a Dictionary first of all. For this, I have this class:
public class Properties : INotifyPropertyChanged
{
private Dictionary<string, string> _properties;
public Properties()
{
_properties = new Dictionary<string, string>();
}
public async void Load(string uri)
{
Stream input = await connection(uri);
StreamReader rStream = new StreamReader(input);
string line;
while((line = rStream.ReadLine()) != null)
{
if(line != "")
{
int pos = line.IndexOf('=');
string key = line.Substring(0, pos);
string value = line.Substring(pos + 1);
_properties.Add(key, value);
Debug.WriteLine("Key: " + key + ", Value: " + value);
}
}
Debug.WriteLine("Properties dictionary filled with " + _properties.Count + " items.");
}
public async Task<Stream> connection(string uri)
{
var httpClient = new HttpClient();
Stream result = Stream.Null;
try
{
result = await httpClient.GetStreamAsync(uri);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.HResult);
}
return result;
}
public string getValue(string key)
{
string result = "";
try
{
result = _properties[key];
}
catch(Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.HResult);
result = "Not found";
}
return result;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
var Handler = PropertyChanged;
if (Handler != null)
Handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Mainly, the Dictionary contains a Key and a URL to download XML files to each page of the application.
The MainPage, which is the one that is gonna be fill has this code:
public MainPage()
{
this.InitializeComponent();
//Properties dictionary filling
prop = new Properties();
prop.Load("URL");
tab = new Bars.TopAppBar();
bab = new Bars.BottomAppBar();
tABar = this.topAppBar;
actvt = this.Activity;
bABar = this.bottomAppBar;
//Constructor of the UserControl
act = new Home(this, prop);
}
The constructor of the UserControl uses the MainPage as a Callback and the class Properties to look for the URL to download the XML file.
What happens is that, as Properties.Load() is an asynchronous method, is called, then the remaining lines are executed and when the program finishes, then goes back to Load() and fills the Dictionary.
As the Home constructor depends on a Value of Properties I get an Exception.
I have tried to create async voids assigning different priorities to force the program to run first one thing and then the remaining, but it hasn't worked either.
So, summarizing, I need to make sure that Properties is filled in the first place, anyone knows how to do it?
Thanks in advance!
Eva if you want to wait until the Load method is finished you have to change this method to return a Task.
public async Task LoadAsync(string uri)...
Better if you put your code in LoadedEventHandler of your page and make this method async. After that you will be able to await Properties.Load method.
If you want to call this method in constructor you can do it like this:
Task.Run(async () =>{
var task = p.LoadAsync().ConfigureAwait(false);
await task;
}).Wait()
But you have to be aware that deadlock can appear if in LoadAsync method will be await without disabled context switching (ConfigureAwait).

How to declare a public string within a while loop (C#)

I am currently trying to declare a public string within a while loop, as I would like to use it (the string) in other methods
The string in question is "s"
private void CheckLog()
{
bool _found;
while (true)
{
_found = false;
Thread.Sleep(5000);
if (!System.IO.File.Exists("Command.bat")) continue;
using (System.IO.StreamReader sr = System.IO.File.OpenText("Command.bat"))
{
string s = "";
while ((s = sr.ReadLine()) != null)
{
if (s.Contains("mp4:production/CATCHUP/"))
{
_found = true;
break;
}
}
}
}
}
you can't declare public string inside the method.
Try this:
string s = "";
private void CheckLog()
{
bool _found;
while (true)
{
_found = false;
Thread.Sleep(5000);
if (!System.IO.File.Exists("Command.bat")) continue;
using (System.IO.StreamReader sr = System.IO.File.OpenText("Command.bat"))
{
//s = "VALUE";
while ((s = sr.ReadLine()) != null)
{
if (s.Contains("mp4:production/CATCHUP/"))
{
_found = true;
break;
}
}
}
}
}
You should create a global variable and assign that instead for example
public class MyClass
{
public string s;
private void CheckLog() { ... }
}
In any method you might use it remember to check if s.IsNullOrEmpty() to avoid getting a NullPointerException (also I'm assuming that the string should contain something).
Better pass the string as by-ref argument to function, or return it from the function. Declaring it as member doesn't seem to be a good idea.
public string CheckLog(){}

Categories