Home > Blockchain >  How do I properly use async and await in this code?
How do I properly use async and await in this code?

Time:10-26

My Windows Form application freezes when deserializing "all episodes" from a local XML-file. I want to solve this problem by implementing asynchronous programming but unfortunately I do not succeed with the implementation (I am a beginner and it's the first time I use await and async).

Working (but freezing) synchronous execution:

public class EpisodeRepository : IEpisodeRepository<Episode>
{
    SerializerForXml dataManager;
    List<Episode> listOfEpisodes;
    
    public EpisodeRepository()
    {
        dataManager = new SerializerForXml();
        listOfEpisodes = new List<Episode>();
        listOfEpisodes = GetAll();
    }
    public List<Episode> GetAll()
    {
        List<Episode> listOfEpisodesDeserialized = new List<Episode>();
        try
        {
            listOfEpisodesDeserialized = dataManager.DeserializeEpisode());
        }
        catch (Exception)
        {
            throw new SerializerException("Episode.xml", "Error when deserialize");
        }

        return listOfEpisodesDeserialized;
    } 
}
public interface IEpisodeRepository<T> : IRepository<T> where T : class
{
    List<T> GetAllFeed(string url);
}

public interface IRepository<T> where T : class
{
    void Create(T entity);
    void Delete(int index);
    void Update(int index, T entity);
    void SaveChanges();
    List<T> GetAll();
    int GetIndex(string name);
}

My attempt to solve it with async and await:

I changed the interface method in IRepository to Task<List<T>> GetAll(); and then I changed the method to:

public async Task<List<Episode>> GetAll()
        {
            List<Episode> listOfEpisodesDeserialized = new List<Episode>();
            try
            {
                listOfEpisodesDeserialized  = await Task.Run(() => dataManager.DeserializeEpisode());
            }
            catch (Exception)
            {
                throw new SerializerException("Episode.xml", "Error when deserialize");
            }

            return listOfEpisodesDeserialized;
        } 

This code won't compile because the method returns

"System.Threading.Tasks.Task<System.Collections.Generic.List<Models.Episode>>" when the field is of type "System.Collections.Generic.List<Models.Episode>".

This code also causes some errors to other methods using GetAll().FirstOrDefault(..).

CodePudding user response:

You should fix a few things to make the code work.

First, in your repository interface change the return type of the GetAll() function like this:

public interface IRepository<T> where T : class
{
    void Create(T entity);
    void Delete(int index);
    void Update(int index, T entity);
    void SaveChanges();
    Task<List<T>> GetAll(); // <= Wrapped in a "Task"
    int GetIndex(string name);
}

Then find all the places where that function was called, and put an await before the call. Also don't forget to make those caller functions async as well, and their calls await.

CodePudding user response:

Quick solution with Lazy

  public EpisodeRepository()
    {
        dataManager = new SerializerForXml();
        listOfEpisodesLazy = new Lazy<List<Episode>>(() => GetAll());
    }

  private listOfEpisodes => listOfEpisodesLazy.Value;

Solution with deporting the heavy processing in a Task. But you must add a check before using listOfEpisodes variable.

  public EpisodeRepository()
    {
        dataManager = new SerializerForXml();
        listOfEpisodes = null;
        PopulateListOfEpisodes();
    }

  private async Task PopulateListOfEpisodes()
  {
    return await Task.Run(() => {
        listOfEpisodes  = GetAll();
    };
  }

In any case, heavy operations should be avoided in the constructor.

CodePudding user response:

Due to CPU-bound nature of your code I'd suggest wrapping it in Task.Run directly in WinForms app.

public async void Button_Click(object sender, MouseEventArgs agrs)
{
    try
    {
         List<Episode> data = await Task.Run(() => repository.GetAll());
         // Update UI
    }
    catch (Exception e)
    { 
        // Handle exception
    }
}

There's an excellent article which explains why: https://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html

And the reason why I wrapped everything in a try-catch is an async void method which tend to be error-prone and basically will swallow your exception without populating it back to the UI thread.

Good article on that too https://johnthiriet.com/removing-async-void/

  • Related