Why is this ASP.NET method blocking, when it should be asynchronous? - c#

Given the following method signature in a WCF service:
public string Query(string request)
{
using (Log.BeginTimedOperation("Persist request"))
{
var messageCorrelationId = Guid.NewGuid().ToString();
var payloadURI = PayloadHelper.GetFullPath(messageCorrelationId);
PayloadHelper.PersistPayloadWithPath(request, payloadURI);
Log.Information("My service request {MessageCorrelationId} {RequestPayloadPath}", messageCorrelationId, payloadURI);
}
// DoWork here, code removed for brevity
return result;
}
and the corresponding extension methods:
public static string GetFullPath(string messageCorrelationId)
{
var folderDate = DateTime.Now.ToString("yyyyMMdd");
var folderHour = DateTime.Now.ToString("HH");
var logFolder = Path.Combine(ConfigurationManager.AppSettings["NetworkFiler"], "Payloads", folderDate, folderHour);
Directory.CreateDirectory(logFolder);
var fileName = $"{messageCorrelationId}-{"MyWCFService"}-{$"{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}-{Guid.NewGuid():N}"}.{"xml"}";
return Path.Combine(logFolder, fileName);
}
public static void PersistPayloadWithPath(string text, string path)
{
var task = WriteFileAsync(path, text);
task.GetAwaiter().GetResult();
}
private static async Task WriteFileAsync(string path, string text)
{
try
{
byte[] buffer = Encoding.Unicode.GetBytes(text);
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "WriteFileAsync");
}
}
This code will block, if for instance the file is being interrogated by anti-virus (guess) or IO slowness to the filer.
So here is the great debate, calling a asynchronous method from a synchronous method in ASP.NET. To this day I still don't know if there is a reliable way to create a fire and forget mechanism. It's not that I don't care about the failure, I do, that 'should be' handled by the catch statement and the static Serilog instance.
While writing this version of the post, it dawned on me that maybe one of the problems might be in fact the logger and it's async wrapper around the File Sink.. will test that in a few.
Any help is appreciated with this bugger.
Thank you,
Stephen
UPDATE-ASYNC
public async Task<string> QueryAsync(string request)
{
using (Log.BeginTimedOperation("PersistAsync-Request"))
{
var messageCorrelationId = Guid.NewGuid().ToString();
var payloadURI = PayloadHelper.GetFullPath(messageCorrelationId);
await PayloadHelper.PersistPayloadWithPathAsync(request, payloadURI).ConfigureAwait(false);
Log.Information("My service request {MessageCorrelationId} {RequestPayloadPath}", messageCorrelationId, payloadURI);
}
// DoWork here, code removed for brevity
return result;
}
public static string GetFullPath(string messageCorrelationId)
{
var folderDate = DateTime.Now.ToString("yyyyMMdd");
var folderHour = DateTime.Now.ToString("HH");
var logFolder = Path.Combine(ConfigurationManager.AppSettings["NetworkFiler"], "Payloads", folderDate, folderHour);
Directory.CreateDirectory(logFolder);
var fileName = $"{messageCorrelationId}-MyWCFService-{DateTime.Now:yyyy-MM-dd-HH-mm-ss-fff}-{Guid.NewGuid():N}.xml";
return Path.Combine(logFolder, fileName);
}
public static async Task PersistPayloadWithPathAsync(string text, string path)
{
await WriteFileAsync(path, text).ConfigureAwait(false);
}
private static async Task WriteFileAsync(string path, string text)
{
try
{
byte[] buffer = Encoding.Unicode.GetBytes(text);
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
}
}
catch
{
// ignored
}
}
Still blocking randomly, lick every 20-30 requests

So it's blocking on the new FileStream(). Looking at the source code, the constructor calls a method called Init(), which actually ends up opening the file. So it is doing I/O in the constructor, which really it shouldn't be since you can't await it.
Setting useAsync should make it run async, if it can. But maybe it can't open the file from a network drive asynchronously.
So your best bet is to just run it inside Task.Run() to guarantee it doesn't block.
The following only applies to .NET Core (unfortunately):
You would be better off using File.WriteAllTextAsync, which would actually make your life easier:
await File.WriteAllTextAsync(path, text);
The documentation of that doesn't really explain anything for some reason, but it works the same as File.WriteAllText (it's just async):
Creates a new file, write the contents to the file, and then closes the file. If the target file already exists, it is overwritten.
So, exactly what you're doing in your code, but this way you can await the whole operation (including opening the file).

I think it waits becuse you use
task.GetAwaiter().GetResult();
If you don't need the result, just remove this line. It should work fine.
If you need the result you should make PersistPayloadWithPath function also async.

The problem is your WriteFileAsync method. It actually runs synchronously until it hits the first await (that's how async/await works). I believe it hangs at new FileStream(...).
If you want to just fire-end-forget form synchronous code, this should be enough:
public static void PersistPayloadWithPath(string text, string path)
{
Task.Run(async () => await WriteFileAsync(path, text));
}
The code above should help you when you don't have async alternatives. However as Gabriel Luci suggested in his answer you should go with await File.WriteAllTextAsync(path, text); as it's probably optimized for async work.
You can still use Task.Run(...); with await File.WriteAllTextAsync(path, text); in fire-and-forget scenario.
Just beware that exceptions from inside the task (WriteFileAsync) won't propagate to the calling thread. This won't be the problem in your case since the whole WriteFileAsync method's body is inside try-catch block, where you log the exception.
EDIT
To illustrate how the threads behave in async/await methods, play around with the following example (try all 3 ways the Bar function is ran):
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main thread: {Thread.CurrentThread.ManagedThreadId}");
Task.Run(async () => await Bar());
// Task.Run(() => Bar());
// Bar();
Console.ReadLine();
}
static async Task Bar()
{
Console.WriteLine($"Bar thread before await: {Thread.CurrentThread.ManagedThreadId}");
await Foo();
Console.WriteLine($"Bar thread after await: {Thread.CurrentThread.ManagedThreadId}");
}
static async Task Foo()
{
Console.WriteLine($"Foo thread before await: {Thread.CurrentThread.ManagedThreadId}");
var c = new WebClient();
var source = await c.DownloadStringTaskAsync("http://google.com");
Console.WriteLine($"Foo thread after await: {Thread.CurrentThread.ManagedThreadId}");
}
}
}

The root caused was determined to be an incorrectly configured StealthAudit, a tool that we use to audit our filers. Once the configuration was adjusted everything works as expected.

Related

Get return value of method from TaskCompletionSource

I have a method RenderReport which generates a PDF file (byte[]). This can sometimes hang indefinitely. It should take no more than 15 seconds to complete when successful. Hence, I'm using a TaskCompletionSource to be able to limit the execution time and throw a TimeoutException if it exceeds the timeout.
However, what I can't determine is: how do you provide the byte[] file returned by RenderReport to the SetResult in the following code? longRunningTask.Wait returns a boolean and not the file so where do you get the file from?
I don't want to use longRunningTask.Result as that can introduce deadlock issues. Here's my code:
public async Task RunLongRunningTaskAsync()
{
Task<byte[]> longRunningTask = Task.Run(() => RenderReport());
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
Task toBeAwaited = tcs.Task;
new Thread(() => ThreadBody(longRunningTask, tcs, 15)).Start();
await toBeAwaited;
}
private void ThreadBody(Task<byte[]> longRunningTask, TaskCompletionSource<byte[]> tcs, int seconds)
{
bool completed = longRunningTask.Wait(TimeSpan.FromSeconds(seconds));
if (completed)
// The result is a placeholder. How do you get the return value of the RenderReport()?
tcs.SetResult(new byte[100]);
else
tcs.SetException(new TimeoutException("error!"));
}
private byte[] RenderReport()
{
using (var report = new Microsoft.Reporting.WinForms.LocalReport())
{
// Other logic here...
var file = report.Render("PDF", null, out _, out _, out _, out _, out var warnings);
if (warnings.Any())
{
// Log warnings...
}
return file; // How do I get this file?
}
}
You only risk deadlocks if you synchronously wait for a Task to complete.
If you know that longRunningTask has completed, it's perfectly safe to access longRunningTask.Result. So just do:
if (completed)
tcs.SetResult(longRunningTask.Result);
else
tcs.SetException(new TimeoutException("error!"));
And even then it's more complex than this: your case won't deadlock even if you do synchronously wait for longRunningTask to complete, as longRunningTask doesn't rely on any awaits. It's well worth understanding why this deadlock situation can occur.
That said, abandoning a thread which has got stuck somewhere sounds like a terrible idea: the thread will just sit around forever leaking resources, and you'll accumulate them as more reports hang, until you run out of memory.
If you really can't figure out what's going on, I'd recommend writing a small wrapper application around the report generation, and run that as a separate process. Killing processes is well-supported, and lets you ensure that nothing is leaked. You can return the bytes of the report back through standard output.
What works for me is to use ContinueWith:
This shows a first run with a successful retrieval in the specified 2-seconds and then a second run where it times out.
So it's just one approach, but is this helpful at all?
using System;
using System.Threading;
using System.Threading.Tasks;
namespace task_completion
{
class Program
{
static void Main(string[] args)
{
runAsync();
Console.ReadKey();
}
static async void runAsync()
{
// Normal
await longRunningByteGetter(1000, new CancellationTokenSource(2000).Token)
.ContinueWith((Task<byte[]> t)=>
{
switch (t.Status)
{
case TaskStatus.RanToCompletion:
var bytesReturned = t.Result;
Console.WriteLine($"Received {bytesReturned.Length} bytes");
break;
default:
Console.WriteLine(nameof(TimeoutException));
break;
}
});
// TimeOut
await longRunningByteGetter(3000, new CancellationTokenSource(2000).Token)
.ContinueWith((Task<byte[]> t) =>
{
switch (t.Status)
{
case TaskStatus.RanToCompletion:
var bytesReturned = t.Result;
Console.WriteLine($"Received {bytesReturned.Length} bytes");
break;
default:
Console.WriteLine(nameof(TimeoutException));
break;
}
});
}
async static Task<Byte[]> longRunningByteGetter(int delay, CancellationToken token)
{
await Task.Delay(delay, token); // This is a mock of your file retrieval
return new byte[100];
}
}
}

Why is the using variable disposing itself before leaving the method?

using PuppeteerSharp;
using System;
using System.Threading.Tasks;
namespace Video_Handler
{
public class VideoHost
{
private static bool Is_HTTPS_URL(string url)
{
Uri uriResult;
bool result = Uri.TryCreate(url, UriKind.Absolute, out uriResult) && (uriResult.Scheme == Uri.UriSchemeHttps);
return result;
}
#region BrowserCreationCode
private static async Task FectchBrowserIfItIsNotThere()
{
var browserFetcher = new BrowserFetcher();
await browserFetcher.DownloadAsync();
}
internal static async Task<Page> GetPageAsync(string url, bool isPageInvisible = true, Action<Page> action)
{
if(!Is_HTTPS_URL(url))
{
throw new ArgumentException($"The url | {url} | is not an https url.");
}
await FectchBrowserIfItIsNotThere();
await using var browser = await Puppeteer.LaunchAsync(
new LaunchOptions { Headless = isPageInvisible });
await using var page = await browser.NewPageAsync();
await page.GoToAsync(url);
action(page);
return page;
}
}
}
//Boiler Plate code
async static void Main()
{
Action<Page> action = (page) => Foo(page);
await GetPageAsync("https://google.com", false , action);
}
public static async Task Foo(Page p){
await p.EvaluateExpression("//fill google search bar"); //first line
await p.EvaluateExpression("//click search button"); //second line
await p.WaitForNavigationAsync(); //third line
await p.EvaluateExpression("alert("hi")"); //fourth line
}
I am having an issue where when I send the page variable to the action which takes in a page variable it causes the page or browser variable to be disposed. From what I understand about using, this should only happen if I leave the function's parenthesis. I have read Microsoft's documentation on using and I can't find anything that tells me that the page variable disposes itself upon going to another function within the function the using variable is in.
Also, occasionally the code will be able to evaluate some of the function's expressions but it usually fails by the third. However, if I debug and run step by step, it always fails on the first line. This is what led me to believe it is a disposal issue.
To anyone wondering why I have not just removed the using from browser and page variables and dispose of them myself, I have two reasons. One, I don't have experience with disposing unmanaged code and when I tried to add the dispose to the finalizer/destructor it doesn't work and Secondly, I don't want to write code to do what the language already does for me.
The issue was using Action instead of Func<Page, Task>.
Action always returns a void. Since the function Action was calling was a async method but was not sending the method's output it causes the function that called Action to exit which released the unmanged resources. The Func<Page, Task> retains the output so it can be awaited so the main thread is blocked till completion preventing the unmanaged resources from being release early.
async static void Main()
{
Func<FileStream,Task> func = (FileStream fs) => Foo(fs); //Input to function is FileStream and Output of Function is Task
Action<FileStream> action = (FileStream fs) => Foo(fs); //Equivalent to Func<FileStream , void>
using var fs = new FileStream();
//action(fs); //This can fail if the main thread is finished before it
await func(fs); //This will succeed since the main thread is blocked till completion
}
async static Task Foo(FileStream fs) => await fs.SomeLongAsyncMethod();

Writing to file asynchronously

I'm having a small problem implementing async methods into my code as it is my first time using it. I am having trouble writing to a file, when I make the method synchronous it works fine, however, when using async - nothing happens.
Here is the code I am currently using:
static async Task WriteFile()
{
string path = $#"C:\Users\{Environment.UserName}\FileToWrite.js";
if (File.Exists(path))
{
try
{
string value;
using (StreamReader reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("AsyncTest.Resources.data.txt")))
{
value = await reader.ReadToEndAsync();
}
using (StreamWriter writer = new StreamWriter(path))
{
await writer.WriteAsync(value);
}
}
catch
{
Debug.WriteLine("Error writing to file.");
}
}
}
I am calling this method like this
await WriteFile(path);
Data in Resources.data.txt:
This is a test, the text file should contain this message.
Any help would be greatly appreciated, thank you!
If this is a console application, then I am guessing your Main method is not returning a Task, which means that your application will exit before the task is complete.
The await keyword will return when it acts on an incomplete Task. Usually it will return its own Task that the caller can use to wait until it's complete. But if the method is void, then it can't return anything and nothing is able to wait for it.
So if you have something like this:
public static async void Main() {
await WriteFile(path);
}
Then as soon as the I/O request is made to the file system, WriteFile will return a Task, then the await in Main will return nothing (because Main is void) and your program will end.
If you are using at least C# 7.1, then you can return a Task from Main:
public static async Task Main() {
await WriteFile(path);
}
And .NET knows that it needs to keep the application going until the Task is complete.
If you cannot use C# 7.1, then you are better off using synchronous code.
Change your method to return a condition of success. This will ensure to run through all of the code prior to function exit.
static async Task<bool> WriteFile(string path)
{
if (File.Exists(path))
{
string value = "";
try
{
using (StreamReader reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("AsyncTest.Resources.data.txt")))
{
value = await reader.ReadToEndAsync();
}
}
catch
{
Debug.WriteLine("Error opening resource.");
return false;
}
try
{
using (StreamWriter writer = new StreamWriter(path))
{
await writer.WriteAsync(value);
}
}
catch
{
Debug.WriteLine("Error writing to file.");
return false;
}
return true;
}
Debug.WriteLine("Error file does not exist.");
return false;
}

Why is MS forcing me into an asynchronous operation?

I need to read a numeric value (a version number) from a text file in my resources.
I compare this version number to the version number of an installed component.
If the version number in the resources is higher than the installed version, I copy the new component (a database) from my resources to a local directory where the user can use it.
I need to do this synchronously because my application can't work without the database.
However, I don't see any way to do it synchronously.
MS forces me to do it with an async task like this:
private async Task<string> ResourcesReadTextFile(string uFileName)
{
string sRet = "";
try
{
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(cstAssets + uFileName));
using (var inputStream = await file.OpenReadAsync())
using (var classicStream = inputStream.AsStreamForRead())
using (var streamReader = new StreamReader(classicStream))
{
while (streamReader.Peek() >= 0)
{
sRet = streamReader.ReadLine();
}
}
}
catch (Exception ex)
{
Debug.Assert(false);//check here
}
return sRet;
}
Now I've encountered a situation where the app started before the database was copied over to the local directory as the copying also needs to be done asynchronously, there simply isn't any way to do it synchronous.
There is no such function as StorageFile.Copy().
What I'm therefore using is:
private async void pCopyFromResourcesToLocal(string uFileName)
{
// Cant await inside catch, but this works anyway
try
{
StorageFile storfile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(cstAssets + uFileName));
await storfile.CopyAsync(ApplicationData.Current.LocalFolder);
}
catch (Exception ex)
{
Debug.WriteLine("");
}
}
This drives me crazy.
People write that Async should be embraced and hugged and appreciated, but in my case it causes nothing but trouble.
I don't see any way of making this thing synchronous, and I wonder why MS forces me to do it that way.
Any help very much appreciated.
Thank you.
Code as a screenshot:
Edit: I've added the top methods here:
public static async Task<DB> InitAppDb()
{
IFileHelper helper = DependencyService.Get<IFileHelper>();
string path = await helper.GetFilePathAndCopyFromResourcesIfNotPresent("tablet.db");
return (_dbApp = new DB(path));
}
public async Task CopyDatabaseIfNotExists(string uFileName)
{
IsolatedStorageFile nExpectedFolder = IsolatedStorageFile.GetUserStoreForApplication();
bool bCopyNewDB = false;
Task<bool> datatask = pResourceIsNewer(uFileName);
bCopyNewDB = await datatask;
if (! bCopyNewDB)
{
try
{
await ApplicationData.Current.LocalFolder.GetFileAsync(uFileName); //nExpectedFolder.GetFileAsync(dbPath);/// ApplicationData.Current.LocalFolder.GetFileAsync("preinstalledDB.db");
// No exception means it exists
return;
}
catch (System.IO.FileNotFoundException)
{
// The file obviously doesn't exist
}
}
pCopyFromResourcesToLocal(uFileName);
}
private async Task<bool>pResourceIsNewer(string uPath)
{
string sFileNameAppDBVersion =uPath + ".txt";
if (IsolatedStorageFileExist(sFileNameAppDBVersion))
{
int iAppDBVersionInstalled = Convert.ToInt32(IsolatedStorageReadTextFile(sFileNameAppDBVersion));
Task<string> datatask = ResourcesReadTextFile(sFileNameAppDBVersion);
string s = await datatask;
int iAppDBResources = Convert.ToInt32(s);
bool b = (iAppDBResources > iAppDBVersionInstalled);
return b;
}
else
{
return true;
}
}
All you have to do is:
//private async void pCopyFromResourcesToLocal(string uFileName) { ... }
private async Task pCopyFromResourcesToLocal(string uFileName) { ... }
and then you can await it:
//pCopyFromResourcesToLocal(uFileName);
await pCopyFromResourcesToLocal(uFileName);
and it will all be completed before you call return (_dbApp = new DB(path));
Nothing in this async/await chain can happen out of order.
When you say that your app can't work without the database, remember that using an await keyword does just that, so the following line of code will not execute until after the async call returns. You can structure your code such that it is responsive while you wait for the DB to come back online.
However, you can force your function to be synchronous by using a construct such as:
StorageFile.GetFileFromApplicationUriAsync(new Uri(cstAssets + uFileName)).GetAwaiter().GetResult();
Or, even better, have a look at the JoinableTaskFactory.
Any asynchronous API method can be made synchronous by simply tagging .GetAwaiter().GetResult() on the end, as #pm_2 says.
In the case of Task<T> results, you can simply use .Result, and for Task results, .Wait().
You can either write an asynchronous function to read your file, then wait for its result at the top level, or you can write a synchronous method and wait for the result of every call it makes to async functions.
So Microsoft is not forcing you into anything: it's just providing a simpler API with the lowest common denominator of both asynchronous and synchronous workflows.

Convert synchronous zip operation to async

We got an existing library where some of the methods needs to be converted to async methods.
However I'm not sure how to do it with the following method (errorhandling has been removed). The purpose of the method is to zip a file and save it to disk. (Note that the zip class doesn't expose any async methods.)
public static bool ZipAndSaveFile(string fileToPack, string archiveName, string outputDirectory)
{
var archiveNameAndPath = Path.Combine(outputDirectory, archiveName);
using (var zip = new ZipFile())
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
zip.Comment = $"This archive was created at {System.DateTime.UtcNow.ToString("G")} (UTC)";
zip.AddFile(fileToPack);
zip.Save(archiveNameAndPath);
}
return true;
}
An implementation could look like this:
public static async Task<bool> ZipAndSaveFileAsync(string fileToPack, string archiveName, string outputDirectory)
{
var archiveNameAndPath = Path.Combine(outputDirectory, archiveName);
await Task.Run(() =>
{
using (var zip = new ZipFile())
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
zip.Comment = $"This archive was created at {System.DateTime.UtcNow.ToString("G")} (UTC)";
zip.AddFile(fileToPack);
zip.Save(archiveNameAndPath);
}
});
return true;
}
Which just seems wrong. The client could just call the sync method using Task.Run
Please, anyone got any hints on how to transform it into a async method ?
Which just seems wrong. The client could just call the sync method
using Task.Run
Spot on. By wrapping synchronous code in Task.Run() the library is now using the client's threadpool resources without it being readily apparent. Imagine what could happen to the client's threadpool if all libraries took this approach? Long story short, just expose the synchronous method and let the client decide if it wants to wrap it in Task.Run().
Having said that, if the ZipFile object had async functionality (e.g. had a SaveAsync() method) then you could make the outer method async as well. Here's an example of how that would look:
public static async Task<bool> ZipAndSaveFileAsync(string fileToPack, string archiveName, string outputDirectory)
{
var archiveNameAndPath = Path.Combine(outputDirectory, archiveName);
using (var zip = new ZipFile())
{
// do stuff
await zip.SaveAsync(archiveNameAndPath);
}
return true;
}
As a temporarily solution, I would introduce an extension method:
public static class ZipFileExtensions
{
public static Task SaveAsync(this ZipFile zipFile, string filePath)
{
zipFile.Save(filePath);
return Task.FromResult(true);
}
}
Then the usage would be:
public static async Task<bool> ZipAndSaveFileAsync(string fileToPack, string archiveName, string outputDirectory)
{
var archiveNameAndPath = Path.Combine(outputDirectory, archiveName);
using (var zip = new ZipFile())
{
...
await zip.SaveAsync(archiveNameAndPath).ConfugureAwait(false);
}
return true;
}
Implementing synchronous tasks does not violate anything (talking about Task.FromResult)
Submit a request to https://github.com/jstedfast/Ionic.Zlib asking for an async support in the library due to IO operations
Hope that's done eventually, and then you can upgrade the Ionic.Zlib in your app, delete the ZipFileExtensions, and continue using async version of the Save method (this time built into the library).
Alternatively, you can clone the repo from GitHub, and add SaveAsync by yourself, the submit a pull request back.
It's just not possible to 'convert' a sync method to an async if a library does not support it.
From performance standpoint, this might not be the best solution, but from management point of view, you can decouple stories "Convert everything to async" and "Improve app performance by having Ionic.Zlib async", what makes your backlog more granular.
public static Task<bool> ZipAndSaveFileAsync(string fileToPack, string archiveName, string outputDirectory)
{
return Task.Run(() =>
{
var archiveNameAndPath = Path.Combine(outputDirectory, archiveName);
using (var zip = new ZipFile())
{
zip.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
zip.Comment = $"This archive was created at {System.DateTime.UtcNow.ToString("G")} (UTC)";
zip.AddFile(fileToPack);
zip.Save(archiveNameAndPath);
}
return true;
});
}
Then use like this
public async Task MyMethod()
{
bool b = await ZipAndSaveFileAsync();
}
Some of the answers suggest that zipping a file is not a process that you should do asynchronously. I don't agree with this.
I can imagine that zipping files is a process that might take some time. During this time you want to keep your UI responsive or you want to zip several files simultaneously, or you want to upload a zipped file while zipping the next one/
The code you show is the proper way to make your function asynchronous. You question whether it is useful to create such a small method. Why not let the users call Task.Run instead of call your async function?
The reason for this is called information hiding. By creating the async function you're hiding how you zip asynchronously, thus relieving others from knowing how to do this.
Besides, information hiding gives you the freedom to change the internals of the procedure as long as you don't change the pre- and postcondition.
One of the answers said that your function still is not asynchronous. That is not true. Callers of your function may call your async function without awaiting for it. While the task is zipping, the caller may do other things. As soon as it needs the boolean result of the task if can await for the task.
Example of usage:
private async Task DoSomethingSimultaneously()
{
var taskZipFileA = ZipAndSaveFileAsync(fileA, ...)
// while this task is zipping do other things,
// for instance start zipping file B:
var taskZipFileB = ZipAndSaveFileAsync(fileB, ...)
// while both tasks are zipping do other things
// after a while you want to wait until both files are finished:
await Task.WhenAll(new Task[] {taskZipFileA, taskZipFileB});
// after the await, the results are known:
if (taskZipFileA.Result)
{
// process the boolean result of taskZipFile A
}
Note the difference between Task.WaitAll and Task.WhenAll
In async - await you use Task.WhenAll. The return is a Task, so you can
await Task.WhenAll (...)
For proper async-await, all functions that call any async function need to be async themselves and return a Task (instead of void) or Task<TResult> instead of TResult. There is one exception: the event handler may return void.
private async void OnButton1_clicked(object sender, ...)
{
bool zipResult = await SaveAndZipFileAsync(...);
ProcessZipResult(zipResult);
}
Using this method your UI keeps responsive. You don't have to call Task.Run
If you have a non-async function and want to start zipping while doing something else, your non-async function has to call Task.Run. As the function is not async it can't use await. When it needs the result of task.Run it needs to use Task.Wait, or Task.WaitAll
private void NonAsyncZipper()
{
var taskZipFileA = Task.Run ( () => ZipAndSaveFileAsync(...);
// while zipping do other things
// after a while when the result is needed:
taskZipFileA.Wait();
ProcesZipResult(taskZipFileA.Result);
}
If it's possible to get the binary data from your Zip library after the compression, then instead of using this library to save the file, use .NET IO libraries to save it.
EDIT:
There is no point in using async for CPU bound operations (such as compression). In your case, the only benefit you can get from async is when you save the file to the disk. I thought that's what you were asking for.

Categories

Resources